//PL 2018-12-19: typeInferenceOld nach typeInference uebertragen
package de.dhbwstuttgart.core;

import de.dhbwstuttgart.bytecode.Codegen;
import de.dhbwstuttgart.bytecode.FunNGenerator;
import de.dhbwstuttgart.environment.CompilationEnvironment;
import de.dhbwstuttgart.environment.DirectoryClassLoader;
import de.dhbwstuttgart.exceptions.CompilerWarning;
import de.dhbwstuttgart.exceptions.DebugException;
import de.dhbwstuttgart.exceptions.WarningsException;
import de.dhbwstuttgart.parser.JavaTXParser;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.antlr.Java17Parser;
import de.dhbwstuttgart.parser.scope.GenericsRegistry;
import de.dhbwstuttgart.parser.SyntaxTreeGenerator.SyntaxTreeGenerator;
import de.dhbwstuttgart.parser.antlr.Java17Parser.SourceFileContext;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.parser.scope.JavaClassRegistry;
import de.dhbwstuttgart.server.SocketClient;
import de.dhbwstuttgart.server.SocketFuture;
import de.dhbwstuttgart.server.packet.SetAutoclosePacket;
import de.dhbwstuttgart.server.packet.UnifyRequestPacket;
import de.dhbwstuttgart.server.packet.UnifyResultPacket;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.factory.ASTFactory;
import de.dhbwstuttgart.syntaxtree.factory.UnifyTypeFactory;
import de.dhbwstuttgart.syntaxtree.statement.Block;
import de.dhbwstuttgart.syntaxtree.type.ExtendsWildcardType;
import de.dhbwstuttgart.syntaxtree.type.GenericRefType;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.syntaxtree.type.SuperWildcardType;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.syntaxtree.type.TypeVisitor;
import de.dhbwstuttgart.syntaxtree.type.Void;
import de.dhbwstuttgart.syntaxtree.visual.ASTTypePrinter;
import de.dhbwstuttgart.target.generate.ASTToTargetAST;
import de.dhbwstuttgart.target.generate.GenericsResult;
import de.dhbwstuttgart.target.tree.*;
import de.dhbwstuttgart.target.tree.type.*;
import de.dhbwstuttgart.typeinference.constraints.Constraint;
import de.dhbwstuttgart.typeinference.constraints.ConstraintSet;
import de.dhbwstuttgart.typeinference.constraints.Pair;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import de.dhbwstuttgart.typeinference.typeAlgo.TYPE;
import de.dhbwstuttgart.typeinference.unify.PlaceholderRegistry;
import de.dhbwstuttgart.typeinference.unify.RuleSet;
import de.dhbwstuttgart.typeinference.unify.TypeUnify;
import de.dhbwstuttgart.typeinference.unify.UnifyContext;
import de.dhbwstuttgart.typeinference.unify.distributeVariance;
import de.dhbwstuttgart.typeinference.unify.interfaces.IFiniteClosure;
import de.dhbwstuttgart.typeinference.unify.model.FiniteClosure;
import de.dhbwstuttgart.typeinference.unify.model.PairOperator;
import de.dhbwstuttgart.typeinference.unify.model.PlaceholderType;
import de.dhbwstuttgart.typeinference.unify.model.UnifyPair;
import de.dhbwstuttgart.typeinference.unify.model.UnifyType;
import de.dhbwstuttgart.typeinference.unify.TypeUnifyTask;
import de.dhbwstuttgart.typeinference.unify.UnifyResultListener;
import de.dhbwstuttgart.typeinference.unify.UnifyResultListenerImpl;
import de.dhbwstuttgart.typeinference.unify.UnifyResultModel;
import de.dhbwstuttgart.typeinference.unify.UnifyTaskModel;

import de.dhbwstuttgart.util.Logger;
import java.io.*;
import java.lang.Record;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Array;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.io.output.NullOutputStream;

public class JavaTXCompiler {

	// do not use this in any code, that can be executed serverside!
	public static PlaceholderRegistry defaultClientPlaceholderRegistry = new PlaceholderRegistry();
	public static Logger defaultLogger = new Logger();
	private final boolean inferTogether;

	// public static JavaTXCompiler INSTANCE;
	final CompilationEnvironment environment;
	Boolean resultmodel = true;

	private final List<File> sources;
	public final Map<File, SourceFile> sourceFiles = new LinkedHashMap<>();
	public final Set<JavaClassName> input = new HashSet<>();

	public volatile UnifyTaskModel usedTasks = new UnifyTaskModel();
	public final DirectoryClassLoader classLoader;

	public final List<File> classPath;
	private final File outputPath;

	public final Map<String, FunNGenerator.GenericParameters> usedFunN = new HashMap<>();
	public final Set<Integer> usedFunNSuperTypes = new HashSet<>();

	private final Map<JavaClassName, ClassOrInterface> loadedClasses = new HashMap<>();
	public Map<String, byte[]> auxiliaries = new HashMap<>();

	public DirectoryClassLoader getClassLoader() {
		return classLoader;
	}
	public final List<CompilerWarning> generatedWarnings = new ArrayList<>();

	public JavaTXCompiler(File sourceFile) throws IOException, ClassNotFoundException {
		this(Collections.singletonList(sourceFile), List.of(), new File("."), ConsoleInterface.inferTogether);
	}

	public JavaTXCompiler(List<File> sourceFiles) throws IOException, ClassNotFoundException {
		this(sourceFiles, List.of(), new File("."), ConsoleInterface.inferTogether);
	}

	public JavaTXCompiler(List<File> sources, List<File> contextPath, File outputPath, boolean inferTogether) throws IOException, ClassNotFoundException {
		// ensure new default placeholder registry for tests
		defaultClientPlaceholderRegistry = new PlaceholderRegistry();
		this.inferTogether = inferTogether;

		var path = new ArrayList<>(contextPath);
		if (contextPath.isEmpty()) {
			// When no contextPaths are given, the working directory is the sources root
			path.add(new File(System.getProperty("user.dir")));
		}
		if (outputPath != null) path.add(outputPath);
		classLoader = new DirectoryClassLoader(path, ClassLoader.getSystemClassLoader().getParent());
		environment = new CompilationEnvironment(sources, classLoader);
		classPath = path;
		this.outputPath = outputPath;
		this.sources = sources;
		// INSTANCE = this;
	}

	public void warn(CompilerWarning warning) {
		this.generatedWarnings.add(warning);
	}
	public void checkWarnings() {
		for (var warning : this.generatedWarnings) {
			defaultLogger.error("[Warning] " + warning.getMessage());
		}
		if (!generatedWarnings.isEmpty()) {
			throw new WarningsException(generatedWarnings.size() + " warnings in code, aborting!", this.generatedWarnings);
		}
	}

	private List<SourceFileContext> generateSources(List<File> sources) throws IOException, ClassNotFoundException {
		// TODO This should maybe be moved elsewhere
		var treeList = new ArrayList<SourceFileContext>(sources.size());
		for (var s : sources) {
			var tree = (Java17Parser.SrcfileContext) JavaTXParser.parse(s);
			treeList.add(tree);
			// Find defined classes
			var pkgName = tree.packageDeclaration() != null ? tree.packageDeclaration().qualifiedName().getText(): "";
			for (var ctx : tree.classOrInterface()) {
				if (ctx instanceof Java17Parser.NoclassorinterfaceContext) continue;
				var classContext = (Java17Parser.ClassorinterfacedeclContext) ctx;
				String name = null;
				Java17Parser.GenericDeclarationListContext decl = null;
				if (classContext.classDeclaration() != null) {
					name = classContext.classDeclaration().identifier().getText();
					decl = classContext.classDeclaration().genericDeclarationList();
				} else if (classContext.recordDeclaration() != null) {
					name = classContext.recordDeclaration().identifier().getText();
					decl = classContext.recordDeclaration().genericDeclarationList();
				} else if (classContext.interfaceDeclaration() != null) {
					name = classContext.interfaceDeclaration().identifier().getText();
					decl = classContext.interfaceDeclaration().genericDeclarationList();
				}

				var numberOfGenerics = decl == null ? 0 : decl.genericTypeVar().size();
				var className = pkgName + (!pkgName.isEmpty() ? "." : "") + name;
				classRegistry.addName(className, numberOfGenerics);
				input.add(classRegistry.getName(className));
			}
		}
		return treeList;
	}

	private void addSourceFile(File file, SourceFile sf) {
		sourceFiles.put(file, sf);
	}

	public ClassOrInterface getClass(JavaClassName name) {
		if (loadedClasses.containsKey(name)) return loadedClasses.get(name);
		for (var sf : sourceFiles.values()) {
			for (var clazz : sf.KlassenVektor) {
				if (clazz.getClassName().equals(name)) return clazz;
			}
		}
		if (input.contains(name)) return null;
		try {
			var clazz = classLoader.loadClass(name.toString());
			ClassOrInterface cif = null;
			if (clazz != null) {
				cif = ASTFactory.createClass(clazz);
				loadedClasses.put(name, cif);
			}
			return cif;
		} catch (ClassNotFoundException ignored) {}
		return null;
	}

	public Set<ClassOrInterface> getAvailableClasses(File sourceFile) throws ClassNotFoundException, IOException {
		Set<ClassOrInterface> allClasses = new HashSet<>();// environment.getAllAvailableClasses();
		ClassOrInterface objectClass = ASTFactory.createClass(Object.class);
		var recordClass = ASTFactory.createClass(Record.class);
		allClasses.add(objectClass);
		allClasses.add(recordClass);

		var sf = sourceFiles.get(sourceFile);
		var importedClasses = new ArrayList<ClassOrInterface>();
		for (JavaClassName name : sf.getImports()) {
			importedClasses.addAll(getAvailableClasses(name));
		}
		for (Class c : CompilationEnvironment.loadDefaultPackageClasses(sf.getPkgName(), sourceFile, this)) {
			ClassOrInterface importedClass = ASTFactory.createClass(c);
			importedClasses.add(importedClass);
		}
		sf.availableClasses.addAll(importedClasses);

		SourceFile sf_new = new SourceFile(sf.getPkgName(), sf.KlassenVektor.stream().map(cl -> new ClassOrInterface(cl)).collect(Collectors.toCollection(ArrayList::new)), sf.imports);
		// sf enthaelt neues Source-File, neue Klassen-Objekte und neue
		// ArrayListen-Objekte fuer Fields, Construktoren und Methoden
		// Alle anderen Objekte werden nur kopiert.
		var isCached = false;
		for (var clazz : sf.availableClasses) {
			if (loadedClasses.containsKey(clazz.getClassName())) {
				allClasses.removeIf(cl -> cl.getClassName().equals(clazz.getClassName()));
				var cif = loadedClasses.get(clazz.getClassName());
				allClasses.add(cif);
				isCached = true;
			}
		}

		if (!isCached) {
			sf_new.KlassenVektor.forEach(cl -> addMethods(sf_new, cl, sf.availableClasses, objectClass));
			allClasses.addAll(sf_new.getClasses());
		}

		allClasses.addAll(sf.KlassenVektor);

		return allClasses;
	}

	public ConstraintSet<Pair> getConstraints(File sourceFile) throws ClassNotFoundException, IOException {
		var sf = sourceFiles.get(sourceFile);
		var allClasses = getAvailableClasses(sourceFile);
		TYPE ty = new TYPE(new HashSet<>(sf.availableClasses), allClasses);
		var constraints = ty.getConstraints();
		return constraints;
	}

	void addMethods(SourceFile sf, ClassOrInterface cl, List<ClassOrInterface> importedClasses, ClassOrInterface objectClass) {
		if (!cl.areMethodsAdded()) {
			ClassOrInterface superclass = null;
			Optional<ClassOrInterface> optSuperclass = importedClasses.stream().filter(x -> x.getClassName().equals(cl.getSuperClass().getName())).findFirst();
			if (optSuperclass.isPresent()) {
				superclass = optSuperclass.get();
			} else {
				optSuperclass = sf.KlassenVektor.stream().filter(x -> x.getClassName().equals(cl.getSuperClass().getName())).findFirst();
				if (optSuperclass.isPresent()) {
					superclass = optSuperclass.get();
					addMethods(sf, superclass, importedClasses, objectClass);
				} else {
					try {
						var className = cl.getSuperClass().getName().toString();
						superclass = ASTFactory.createClass(classLoader.loadClass(className));
					} catch (ClassNotFoundException e) {
						throw new RuntimeException(e);
					}
				}
			}

			Iterator<RefTypeOrTPHOrWildcardOrGeneric> paraIt = cl.getSuperClass().getParaList().iterator();
			Iterator<GenericTypeVar> tvarVarIt = superclass.getGenerics().iterator();

			HashMap<String, RefTypeOrTPHOrWildcardOrGeneric> gtvs = new HashMap<>();
			while (paraIt.hasNext()) {
				gtvs.put(tvarVarIt.next().getName(), paraIt.next());
			}

            for (Method m : superclass.getMethods()) {
                if (m.isInherited || Modifier.isStatic(m.modifier)) continue;
				// TODO: Add gtvs from method itself

                ParameterList newParaList = new ParameterList(m.getParameterList().getFormalparalist().stream().map(fp -> fp.withType(fp.getType().acceptTV(new TypeExchanger(gtvs)))).collect(Collectors.toCollection(ArrayList::new)), m.getParameterList().getOffset());
                cl.getMethods().add(new Method(m.modifier, m.name, m.getReturnType().acceptTV(new TypeExchanger(gtvs)), newParaList, m.block,
                        // new GenericDeclarationList(newGenericsList,
                        // ((GenericDeclarationList)m.getGenerics()).getOffset()),
                        (GenericDeclarationList) m.getGenerics(), m.getOffset(), true, false));
            }

		}
		cl.setMethodsAdded();
	}

	private Set<ClassOrInterface> getAvailableClasses(JavaClassName name) throws ClassNotFoundException {
		Set<ClassOrInterface> allClasses = new HashSet<>();
		if (input.contains(name)) {
			for (var sf : sourceFiles.values()) {
				for (var cif : sf.KlassenVektor) {
					if (cif.getClassName().equals(name)) {
						allClasses.add(cif);
						return allClasses;
					}
				}
			}
		}
		ClassOrInterface importedClass = ASTFactory.createClass(classLoader.loadClass(name.toString()));
		allClasses.add(importedClass);
		return allClasses;
	}

	public Set<ClassOrInterface> getAvailableClasses(SourceFile forSourceFile) throws ClassNotFoundException {
		// PL 2018-09-18: List durch Set ersetzt, damit die Klassen nur einmal
		// hinzugefuegt werden
		// List<ClassOrInterface> allClasses = new
		// ArrayList<>();//environment.getAllAvailableClasses();
		Set<ClassOrInterface> allClasses = new HashSet<>();

		for (JavaClassName name : forSourceFile.getImports()) {
			allClasses.addAll(getAvailableClasses(name));
		}
		return allClasses;
	}

	/*
	 * public List<ResultSet> typeInferenceOld() throws ClassNotFoundException { List<ClassOrInterface> allClasses = new ArrayList<>();//environment.getAllAvailableClasses(); //Alle Importierten Klassen in allen geparsten Sourcefiles kommen ins FC for(SourceFile sf : this.sourceFiles.values()) { allClasses.addAll(getAvailableClasses(sf)); allClasses.addAll(sf.getClasses()); }
	 *
	 * final ConstraintSet<Pair> cons = getConstraints();
	 *
	 * FiniteClosure finiteClosure = UnifyTypeFactory.generateFC(allClasses); System.out.println(finiteClosure); ConstraintSet<UnifyPair> unifyCons = UnifyTypeFactory.convert(cons);
	 *
	 * TypeUnify unify = new TypeUnify(); Set<Set<UnifyPair>> results = new HashSet<>(); try { File logPath = new File(System.getProperty("user.dir")+"/target/logFiles/"); logPath.mkdirs(); FileWriter logFile = new FileWriter(new File(logPath, "log")); logFile.write("FC:\\" + finiteClosure.toString()+"\n"); for(SourceFile sf : this.sourceFiles.values()) { logFile.write(ASTTypePrinter.print(sf)); } logFile.flush(); Set<List<Constraint<UnifyPair>>> cardProd = unifyCons.cartesianProduct(); for
	 * (List<Constraint<UnifyPair>> xCons : cardProd ){ Set<UnifyPair> xConsSet = new HashSet<>(); for (Constraint<UnifyPair> constraint : xCons) { xConsSet.addAll(constraint); } //.collect(Collectors.toCollection(ArrayList::new)))) System.out.println(xConsSet); Set<String> methodParaTypeVarNames = allClasses.stream().map(x -> x.getMethods().stream().map(y -> y.getParameterList().getFormalparalist() .stream().filter(z -> z.getType() instanceof TypePlaceholder) .map(z ->
	 * ((TypePlaceholder)z.getType()).getName()).collect(Collectors.toCollection( HashSet::new))) .reduce(new HashSet<String>(), (a,b) -> { a.addAll(b); return a;}, (a,b) -> { a.addAll(b); return a;} ) ) .reduce(new HashSet<String>(), (a,b) -> { a.addAll(b); return a;} );
	 *
	 * Set<String> constructorParaTypeVarNames = allClasses.stream().map(x -> x.getConstructors().stream().map(y -> y.getParameterList().getFormalparalist() .stream().filter(z -> z.getType() instanceof TypePlaceholder) .map(z -> ((TypePlaceholder)z.getType()).getName()).collect(Collectors.toCollection( HashSet::new))) .reduce(new HashSet<String>(), (a,b) -> { a.addAll(b); return a;}, (a,b) -> { a.addAll(b); return a;} ) ) .reduce(new HashSet<String>(), (a,b) -> { a.addAll(b); return a;} );
	 *
	 * Set<String> paraTypeVarNames = methodParaTypeVarNames; paraTypeVarNames.addAll(constructorParaTypeVarNames);
	 *
	 * Set<String> returnTypeVarNames = allClasses.stream().map(x -> x.getMethods().stream().filter(y -> y.getReturnType() instanceof TypePlaceholder) .map(z -> ((TypePlaceholder)z.getReturnType()).getName()).collect(Collectors. toCollection(HashSet::new))).reduce((a,b) -> { a.addAll(b); return a;} ).get();
	 *
	 * Set<String> fieldTypeVarNames = allClasses.stream().map(x -> x.getFieldDecl().stream().filter(y -> y.getReturnType() instanceof TypePlaceholder) .map(z -> ((TypePlaceholder)z.getReturnType()).getName()).collect(Collectors. toCollection(HashSet::new))).reduce((a,b) -> { a.addAll(b); return a;} ).get();
	 *
	 * returnTypeVarNames.addAll(fieldTypeVarNames);
	 *
	 * xConsSet = xConsSet.stream().map(x -> { //Hier muss ueberlegt werden, ob //1. alle Argument- und Retuntyp-Variablen in allen UnifyPairs // mit disableWildcardtable() werden. //2. alle Typvariablen mit Argument- oder Retuntyp-Variablen //in Beziehung auch auf disableWildcardtable() gesetzt werden muessen //PL 2018-04-23 if ((x.getLhsType() instanceof PlaceholderType)) { if (paraTypeVarNames.contains(x.getLhsType().getName())) { ((PlaceholderType)x.getLhsType()).setVariance((byte)1);
	 * ((PlaceholderType)x.getLhsType()).disableWildcardtable(); } if (returnTypeVarNames.contains(x.getLhsType().getName())) { ((PlaceholderType)x.getLhsType()).setVariance((byte)-1); ((PlaceholderType)x.getLhsType()).disableWildcardtable(); } } if ((x.getRhsType() instanceof PlaceholderType)) { if (paraTypeVarNames.contains(x.getRhsType().getName())) { ((PlaceholderType)x.getRhsType()).setVariance((byte)1); ((PlaceholderType)x.getRhsType()).disableWildcardtable(); } if
	 * (returnTypeVarNames.contains(x.getRhsType().getName())) { ((PlaceholderType)x.getRhsType()).setVariance((byte)-1); ((PlaceholderType)x.getRhsType()).disableWildcardtable(); } } return x;//HIER DIE JEWEILS RECHT BZW. LINKE SEITE AUF GLEICHE VARIANZ SETZEN WIE DIE JEWEILS ANDERE SEITE }).map( y -> { if ((y.getLhsType() instanceof PlaceholderType) && (y.getRhsType() instanceof PlaceholderType)) { if (((PlaceholderType)y.getLhsType()).getVariance() != 0 &&
	 * ((PlaceholderType)y.getRhsType()).getVariance() == 0) { ((PlaceholderType)y.getRhsType()).setVariance(((PlaceholderType)y.getLhsType( )).getVariance()); } if (((PlaceholderType)y.getLhsType()).getVariance() == 0 && ((PlaceholderType)y.getRhsType()).getVariance() != 0) { ((PlaceholderType)y.getLhsType()).setVariance(((PlaceholderType)y.getRhsType( )).getVariance()); } } return y; } ) .collect(Collectors.toCollection(HashSet::new)); varianceInheritance(xConsSet); Set<Set<UnifyPair>> result =
	 * unify.unifySequential(xConsSet, finiteClosure, logFile, log); //Set<Set<UnifyPair>> result = unify.unify(xConsSet, finiteClosure); System.out.println("RESULT: " + result); logFile.write("RES: " + result.toString()+"\n"); logFile.flush(); results.addAll(result); }
	 *
	 * results = results.stream().map(x -> { Optional<Set<UnifyPair>> res = new RuleSet().subst(x.stream().map(y -> { if (y.getPairOp() == PairOperator.SMALLERDOTWC) y.setPairOp(PairOperator.EQUALSDOT); return y; //alle Paare a <.? b erden durch a =. b ersetzt }).collect(Collectors.toCollection(HashSet::new))); if (res.isPresent()) {//wenn subst ein Erg liefert wurde was veraendert return new TypeUnifyTask().applyTypeUnificationRules(res.get(), finiteClosure); } else return x; //wenn nichts
	 * veraendert wurde wird x zurueckgegeben }).collect(Collectors.toCollection(HashSet::new)); System.out.println("RESULT Final: " + results); logFile.write("RES_FINAL: " + results.toString()+"\n"); logFile.flush(); logFile.write("PLACEHOLDERS: " + PlaceholderType.EXISTING_PLACEHOLDERS); logFile.flush(); } catch (IOException e) { e.printStackTrace(); } return results.stream().map((unifyPairs -> new ResultSet(UnifyTypeFactory.convert(unifyPairs,
	 * generateTPHMap(cons))))).collect(Collectors.toList()); }
	 */
	/**
	 * Vererbt alle Variancen bei Paaren (a <. theta) oder (Theta <. a) wenn a eine Variance !=0 hat auf alle Typvariablen in Theta.
	 *
	 *
	 */
	/*
	 * private void varianceInheritance(Set<UnifyPair> eq) { Set<PlaceholderType> usedTPH = new HashSet<>(); Set<PlaceholderType> phSet = eq.stream().map(x -> { Set<PlaceholderType> pair = new HashSet<>(); if (x.getLhsType() instanceof PlaceholderType) pair.add((PlaceholderType)x.getLhsType()); if (x.getRhsType() instanceof PlaceholderType) pair.add((PlaceholderType)x.getRhsType()); return pair; }).reduce(new HashSet<>(), (a,b) -> { a.addAll(b); return a;} , (c,d) -> { c.addAll(d); return c;});
	 *
	 * ArrayList<PlaceholderType> phSetVariance = new ArrayList<>(phSet); phSetVariance.removeIf(x -> (x.getVariance() == 0)); while(!phSetVariance.isEmpty()) { PlaceholderType a = phSetVariance.remove(0); usedTPH.add(a); //HashMap<PlaceholderType,Integer> ht = new HashMap<>(); //ht.put(a, a.getVariance()); Set<UnifyPair> eq1 = new HashSet<>(eq); eq1.removeIf(x -> !(x.getLhsType() instanceof PlaceholderType && ((PlaceholderType)x.getLhsType()).equals(a))); eq1.stream().forEach(x -> {
	 * x.getRhsType().accept(new distributeVariance(), a.getVariance());}); eq1 = new HashSet<>(eq); eq1.removeIf(x -> !(x.getRhsType() instanceof PlaceholderType && ((PlaceholderType)x.getRhsType()).equals(a))); eq1.stream().forEach(x -> { x.getLhsType().accept(new distributeVariance(), a.getVariance());}); phSetVariance = new ArrayList<>(phSet); phSetVariance.removeIf(x -> (x.getVariance() == 0 || usedTPH.contains(x))); } }
	 */

	public UnifyResultModel typeInferenceAsync(UnifyResultListener resultListener, Writer logFile) throws ClassNotFoundException, IOException {
		List<ClassOrInterface> allClasses = new ArrayList<>();// environment.getAllAvailableClasses();
		// Alle Importierten Klassen in allen geparsten Sourcefiles kommen ins FC
		for (Entry<File, SourceFile> source : this.sourceFiles.entrySet()) {
			SourceFile sf = source.getValue();
			allClasses.addAll(getAvailableClasses(sf));
			allClasses.addAll(sf.getClasses());
			allClasses.addAll(CompilationEnvironment.loadDefaultPackageClasses(sf.getPkgName(), source.getKey(), this).stream().map(ASTFactory::createClass).collect(Collectors.toList()));
		}

		final ConstraintSet<Pair> cons = new ConstraintSet<>();
		for (var f : this.sourceFiles.keySet()) {
			cons.addAll(getConstraints(f));
		}

		Set<Set<UnifyPair>> results = new HashSet<>();
		UnifyResultModel urm = null;
		// urm.addUnifyResultListener(resultListener);
		logFile = logFile == null ? new FileWriter("log_" + sourceFiles.keySet().iterator().next().getName()) : logFile;
		Logger logger = new Logger(logFile, "TypeInferenceAsync");
		UnifyContext context = new UnifyContext(logger, true, urm, usedTasks, defaultClientPlaceholderRegistry);

		IFiniteClosure finiteClosure = UnifyTypeFactory.generateFC(allClasses, logger, getClassLoader(), this, context.placeholderRegistry());
		logger.info(finiteClosure.toString());
		urm = new UnifyResultModel(cons, finiteClosure);
		urm.addUnifyResultListener(resultListener);
		ConstraintSet<UnifyPair> unifyCons = UnifyTypeFactory.convert(this, cons, context.placeholderRegistry());

		Function<UnifyPair, UnifyPair> distributeInnerVars = x -> {
			UnifyType lhs, rhs;
			if (((lhs = x.getLhsType()) instanceof PlaceholderType) && ((rhs = x.getRhsType()) instanceof PlaceholderType) && (((PlaceholderType) lhs).isInnerType() || ((PlaceholderType) rhs).isInnerType())) {
				((PlaceholderType) lhs).setInnerType(true);
				((PlaceholderType) rhs).setInnerType(true);
			}
			return x;

		};
		logger.debug(unifyCons.toString());
		unifyCons = unifyCons.map(distributeInnerVars);
		logger.debug(unifyCons.toString());
		// Set<Set<UnifyPair>> results = new HashSet<>(); Nach vorne gezogen
		logger.debug("FC:\\" + finiteClosure.toString() + "\n");
		for (SourceFile f : this.sourceFiles.values()) {
			logger.debug(ASTTypePrinter.print(f));
		}
		// logFile.flush();
		Set<PlaceholderType> varianceTPHold;
		Set<PlaceholderType> varianceTPH = new HashSet<>();
		varianceTPH = varianceInheritanceConstraintSet(unifyCons);

		/*
		 * PL 2018-11-07 wird in varianceInheritanceConstraintSet erledigt do { //PL 2018-11-05 Huellenbildung Variance auf alle TPHs der Terme auf der jeweiligen //anderen Seite übertragen varianceTPHold = new HashSet<>(varianceTPH); varianceTPH = varianceInheritanceConstraintSet(unifyCons); unifyCons.map( y -> { if ((y.getLhsType() instanceof PlaceholderType) && (y.getRhsType() instanceof PlaceholderType)) { if (((PlaceholderType)y.getLhsType()).getVariance() != 0 &&
		 * ((PlaceholderType)y.getRhsType()).getVariance() == 0) { ((PlaceholderType)y.getRhsType()).setVariance(((PlaceholderType)y.getLhsType( )).getVariance()); } if (((PlaceholderType)y.getLhsType()).getVariance() == 0 && ((PlaceholderType)y.getRhsType()).getVariance() != 0) { ((PlaceholderType)y.getLhsType()).setVariance(((PlaceholderType)y.getRhsType( )).getVariance()); } } return y; } ); } while (!varianceTPHold.equals(varianceTPH));
		 */

		// Set<Set<UnifyPair>> result = unify.unifySequential(xConsSet, finiteClosure,
		// logFile, log);
		// Set<Set<UnifyPair>> result = unify.unify(xConsSet, finiteClosure);
		List<Set<Constraint<UnifyPair>>> oderConstraints = unifyCons.getOderConstraints()/*
																							 * .stream().map(x -> { Set<Set<UnifyPair>> ret = new HashSet<>(); for (Constraint<UnifyPair> y : x) { ret.add(new HashSet<>(y)); } return ret; }).collect(Collectors. toCollection(ArrayList::new))
																							 */;
		TypeUnify.unifyAsync(unifyCons.getUndConstraints(), oderConstraints, finiteClosure, context);

		return urm;
	}

	public List<ResultSet> typeInference(File file) throws ClassNotFoundException, IOException {
		return typeInference(List.of(file));
	}

	public ConstraintSet<?> lastConstraints;

	public List<ResultSet> typeInference(List<File> files) throws ClassNotFoundException, IOException {
		Set<ClassOrInterface> allClasses = new HashSet<>();

		var ressf = new ArrayList<SourceFile>();
		var definedClasses = new HashSet<ClassOrInterface>();
		for (var file : files) {
			var sf = sourceFiles.get(file);
			definedClasses.addAll(sf.KlassenVektor);
			ressf.add(sf);
			allClasses.addAll(getAvailableClasses(file));
			allClasses.addAll(sf.availableClasses);
		}

		TYPE ty = new TYPE(definedClasses, allClasses);
		var cons = ty.getConstraints();
		lastConstraints = cons;

		Set<Set<UnifyPair>> results = new HashSet<>();
		PlaceholderRegistry placeholderRegistry = new PlaceholderRegistry();

		Logger logger = new Logger("TypeInference");

		FiniteClosure finiteClosure = UnifyTypeFactory.generateFC(allClasses.stream().toList(), logger, classLoader, this, placeholderRegistry);
		logger.info(finiteClosure.toString());
		ConstraintSet<UnifyPair> unifyCons = UnifyTypeFactory.convert(this, cons, placeholderRegistry);
		logger.info("xxx1");
		Function<UnifyPair, UnifyPair> distributeInnerVars = x -> {
			UnifyType lhs, rhs;
			if (((lhs = x.getLhsType()) instanceof PlaceholderType) && ((rhs = x.getRhsType()) instanceof PlaceholderType) && (((PlaceholderType) lhs).isInnerType() || ((PlaceholderType) rhs).isInnerType())) {
				((PlaceholderType) lhs).setInnerType(true);
				((PlaceholderType) rhs).setInnerType(true);
			}
			return x;

		};

    logger.debug("Unify:" + unifyCons.toString());
    logger.info("Unify:" + unifyCons.toString());
    unifyCons = unifyCons.map(distributeInnerVars);
    logger.debug("\nUnify_distributeInnerVars: " + unifyCons.toString());
    // Set<Set<UnifyPair>> results = new HashSet<>(); Nach vorne gezogen
    logger.debug("FC:\\" + finiteClosure.toString() + "\n");
	for (var sf : ressf) logger.info(ASTTypePrinter.print(sf));
    // logFile.flush();
    List<UnifyPair> andConstraintsSorted = unifyCons.getUndConstraints().stream()
        .sorted(Comparator.comparing(UnifyPair::getPairOp).thenComparing(UnifyPair::getLhsType, Comparator.comparing(UnifyType::getName)))
        .collect(Collectors.toList());

    logger.info(andConstraintsSorted);

    Set<PlaceholderType> varianceTPHold;
    Set<PlaceholderType> varianceTPH = new HashSet<>();
    varianceTPH = varianceInheritanceConstraintSet(unifyCons);

		/*
		 * PL 2018-11-07 wird in varianceInheritanceConstraintSet erledigt do { //PL 2018-11-05 Huellenbildung Variance auf alle TPHs der Terme auf der jeweiligen //anderen Seite übertragen varianceTPHold = new HashSet<>(varianceTPH); varianceTPH = varianceInheritanceConstraintSet(unifyCons); unifyCons.map( y -> { if ((y.getLhsType() instanceof PlaceholderType) && (y.getRhsType() instanceof PlaceholderType)) { if (((PlaceholderType)y.getLhsType()).getVariance() != 0 &&
		 * ((PlaceholderType)y.getRhsType()).getVariance() == 0) { ((PlaceholderType)y.getRhsType()).setVariance(((PlaceholderType)y.getLhsType( )).getVariance()); } if (((PlaceholderType)y.getLhsType()).getVariance() == 0 && ((PlaceholderType)y.getRhsType()).getVariance() != 0) { ((PlaceholderType)y.getLhsType()).setVariance(((PlaceholderType)y.getRhsType( )).getVariance()); } } return y; } ); } while (!varianceTPHold.equals(varianceTPH));
		 */

    // Set<Set<UnifyPair>> result = unify.unifySequential(xConsSet, finiteClosure,
    // logFile, log);
    // Set<Set<UnifyPair>> result = unify.unify(xConsSet, finiteClosure);
    List<Set<Constraint<UnifyPair>>> oderConstraints = unifyCons.getOderConstraints()// .stream().map(x -> {
    /*
     * Set<Set<UnifyPair>> ret = new HashSet<>(); for (Constraint<UnifyPair> y : x) { ret.add(new HashSet<>(y)); } return ret; }).collect(Collectors.toCollection(ArrayList::new))
     */;
    if (ConsoleInterface.unifyServerUrl.isPresent()) {
        UnifyResultModel urm = new UnifyResultModel(cons, finiteClosure);
        UnifyContext context = new UnifyContext(logger, true, urm, usedTasks, placeholderRegistry);
        SocketFuture<UnifyResultPacket> future = SocketClient.execute(
          UnifyRequestPacket.create(finiteClosure, cons, unifyCons, context.placeholderRegistry())
        );
        SocketClient.execute(SetAutoclosePacket.create());
        return future.get().getResultSet(context);
    }
    else if (resultmodel) {
        /* UnifyResultModel Anfang */
        UnifyResultModel urm = new UnifyResultModel(cons, finiteClosure);
        UnifyResultListenerImpl li = new UnifyResultListenerImpl();
        urm.addUnifyResultListener(li);
		UnifyContext context = new UnifyContext(logger, true, urm, usedTasks, placeholderRegistry);
		TypeUnify.unifyParallel(unifyCons.getUndConstraints(), oderConstraints, finiteClosure, context);
        var finalResults = li.getResults().stream().sorted().toList();
        int i = 0;
        logger.info("RESULT Final: ");
        for (var result : finalResults){
          logger.info("Result: " + i++);
          logger.info(result.getSortedResults());
        }
        logger.info("RES_FINAL: " + li.getResults().toString() + "\n");
        return li.getResults();
    }
    /* UnifyResultModel End */
    else {
        // Set<Set<UnifyPair>> result = unify.unify(unifyCons.getUndConstraints(),
        // oderConstraints, finiteClosure, logFile, log, new UnifyResultModel(cons,
        // finiteClosure));
        UnifyContext context = new UnifyContext(logger, false, new UnifyResultModel(cons, finiteClosure), usedTasks, placeholderRegistry);
        Set<Set<UnifyPair>> result = TypeUnify.unifyOderConstraints(unifyCons.getUndConstraints(), oderConstraints, finiteClosure, context);
        logger.info("RESULT: " + result);
        results.addAll(result);

        results = results.stream().map(x -> {
          Optional<Set<UnifyPair>> res = new RuleSet(placeholderRegistry).subst(x.stream().map(y -> {
            if (y.getPairOp() == PairOperator.SMALLERDOTWC)
              y.setPairOp(PairOperator.EQUALSDOT);
            return y; // alle Paare a <.? b erden durch a =. b ersetzt
          }).collect(Collectors.toCollection(HashSet::new)));
          if (res.isPresent()) {// wenn subst ein Erg liefert wurde was veraendert
            return new TypeUnifyTask(context).applyTypeUnificationRules(res.get(), finiteClosure);
          } else
            return x; // wenn nichts veraendert wurde wird x zurueckgegeben
        }).collect(Collectors.toCollection(HashSet::new));
        logger.info("RESULT Final: " + results);
        logger.info("Constraints for Generated Generics: " + " ???");
        logger.debug("RES_FINAL: " + results.toString() + "\n");
        // logFile.flush();
        logger.debug("PLACEHOLDERS: " + placeholderRegistry);
        // logFile.flush();
		}

		return results.stream().map((unifyPairs -> new ResultSet(UnifyTypeFactory.convert(unifyPairs, Pair.generateTPHMap(cons), placeholderRegistry)))).collect(Collectors.toList());
	}

	/**
	 * Vererbt alle Variancen bei Paaren (a <. theta) oder (Theta <. a) wenn a eine Variance !=0 hat auf alle Typvariablen in Theta.
	 * 
	 *
	 */
	private Set<PlaceholderType> varianceInheritanceConstraintSet(ConstraintSet<UnifyPair> cons) {
		Set<UnifyPair> eq = cons.getAll();
		Set<PlaceholderType> usedTPH = new HashSet<>();
		Set<PlaceholderType> phSet = eq.stream().map(x -> {
			Set<PlaceholderType> pair = new HashSet<>();
			if (x.getLhsType() instanceof PlaceholderType)
				pair.add((PlaceholderType) x.getLhsType());
			if (x.getRhsType() instanceof PlaceholderType)
				pair.add((PlaceholderType) x.getRhsType());
			return pair;
		}).reduce(new HashSet<>(), (a, b) -> {
			a.addAll(b);
			return a;
		}, (c, d) -> {
			c.addAll(d);
			return c;
		});

		ArrayList<PlaceholderType> phSetVariance = new ArrayList<>(phSet);
		phSetVariance.removeIf(x -> (x.getVariance() == 0));
		while (!phSetVariance.isEmpty()) {
			PlaceholderType a = phSetVariance.remove(0);
			usedTPH.add(a);
			// HashMap<PlaceholderType,Integer> ht = new HashMap<>();
			// ht.put(a, a.getVariance());
			// ConstraintSet<UnifyPair> eq1 = cons;
			// eq1.removeIf(x -> !(x.getLhsType() instanceof PlaceholderType &&
			// ((PlaceholderType)x.getLhsType()).equals(a)));
			// durch if-Abfrage im foreach geloest
			cons.forEach(x -> {
				if (x.getLhsType() instanceof PlaceholderType && ((PlaceholderType) x.getLhsType()).equals(a)) {
					x.getRhsType().accept(new distributeVariance(), a.getVariance());
				}
			});
			// ` eq1 = new HashSet<>(eq);
			// eq1.removeIf(x -> !(x.getRhsType() instanceof PlaceholderType &&
			// ((PlaceholderType)x.getRhsType()).equals(a)));
			// durch if-Abfrage im foreach geloest
			cons.forEach(x -> {
				if (x.getRhsType() instanceof PlaceholderType && ((PlaceholderType) x.getRhsType()).equals(a)) {
					x.getLhsType().accept(new distributeVariance(), a.getVariance());
				}
			});
			phSetVariance = new ArrayList<>(phSet); // macht vermutlich keinen Sinn PL 2018-10-18, doch, es koennen neue
			// TPHs mit Variancen dazugekommen sein PL 2018-11-07
			phSetVariance.removeIf(x -> (x.getVariance() == 0 || usedTPH.contains(x)));
		}
		return usedTPH;
	}

	public final JavaClassRegistry classRegistry = new JavaClassRegistry();

	private void parse(SourceFileContext tree, File sourceFile) throws IOException, ClassNotFoundException {
		SyntaxTreeGenerator generator = new SyntaxTreeGenerator(this, classRegistry, new GenericsRegistry(null), sourceFile.getName());
		environment.addClassesToRegistry(classRegistry, tree, sourceFile, this);
		var classes = new ArrayList<ClassOrInterface>();
		var sf = new SourceFile("", classes, generator.imports);
		addSourceFile(sourceFile, sf);
		generator.convert(classes, tree, environment.packageCrawler);
		sf.setPackageName(generator.pkgName);
		sf.imports.addAll(generator.imports);
	}

	public void parseAll() throws IOException, ClassNotFoundException {
		var treeList = generateSources(sources);
		for (var i = 0; i < sources.size(); i++) {
			parse(treeList.get(i), sources.get(i));
		}
	}

	public void generateBytecode() throws ClassNotFoundException, IOException {
		// The compiler has two modes, one is to infer all files together and the other one is to do them in sequence
		if (inferTogether) {
			parseAll();

			var files = sourceFiles.keySet().stream().toList();
			var typeinferenceResult = this.typeInference(files);
			for (var file : files) {
				var sf = sourceFiles.get(file);
				if (sf.isGenerated()) continue;
				var classes = generateBytecode(sf, typeinferenceResult);
				checkWarnings();
				sf.setGenerated();
				writeClassFile(classes, file);
			}
		} else {
			var treeList = generateSources(sources);
			for (var i = 0; i < sources.size(); i++) {
				var file = sources.get(i);
				parse(treeList.get(i), file);

				var sf = sourceFiles.get(file);
				if (sf.isGenerated()) continue;
				var classes = generateBytecode(file);
				checkWarnings();
				sf.setGenerated();
				writeClassFile(classes, file);
			}
		}
	}

	private Map<JavaClassName, byte[]> generateBytecode(File sourceFile) throws ClassNotFoundException, IOException {
		var sf = sourceFiles.get(sourceFile);
		if (sf.isGenerated()) return null;
		List<ResultSet> typeinferenceResult = this.typeInference(sourceFile);
		var classes = generateBytecode(sf, typeinferenceResult);
		sf.setGenerated();
		return classes;
	}

	private Map<SourceFile, List<GenericsResult>> generatedGenerics = new HashMap<>();

	public Map<SourceFile, List<GenericsResult>> getGeneratedGenerics() {
		return generatedGenerics;
	}

	/**
	 * @param outputPath          - can be null, then class file output is in the same directory as the parsed source files
	 * @param typeinferenceResult
	 * @throws IOException
	 */
	public void generateBytecode(File outputPath, List<ResultSet> typeinferenceResult) throws IOException {
		for (File f : sourceFiles.keySet()) {
			HashMap<JavaClassName, byte[]> classFiles = new HashMap<>();
			SourceFile sf = sourceFiles.get(f);
			File path = outputPath;
			if (outputPath == null) {
				path = f.getParentFile(); // Set path to path of the parsed .jav file
			}

			var generatedClasses = generateBytecode(sf, typeinferenceResult);
			writeClassFile(generatedClasses, path, outputPath == null);
		}
	}

	// TODO Maybe move this elsewhere it doesn't really belong here
	private static RefTypeOrTPHOrWildcardOrGeneric toRefType(TargetType type) {
		return switch(type) {
            case TargetExtendsWildcard targetExtendsWildcard ->
				new ExtendsWildcardType(toRefType(targetExtendsWildcard.innerType()), new NullToken());
			case TargetSuperWildcard targetSuperWildcard ->
				new SuperWildcardType(toRefType(targetSuperWildcard.innerType()), new NullToken());
			case TargetGenericType targetGenericType -> new GenericRefType(targetGenericType.name(), new NullToken());
            case TargetPrimitiveType targetPrimitiveType -> toRefType(TargetType.toWrapper(targetPrimitiveType));
            case TargetSpecializedType targetSpecializedType ->
				new RefType(new JavaClassName(targetSpecializedType.name()),
					targetSpecializedType.params().stream().map(JavaTXCompiler::toRefType).toList(), new NullToken()
				);
			case null -> new Void(new NullToken());
        };
	}

	private static ParameterList toParameterList(List<MethodParameter> params) {
		return new ParameterList(params.stream().map(mp
			-> (Pattern) new FormalParameter(mp.pattern().name(), toRefType(mp.pattern().type()), new NullToken())).toList(),
			new NullToken()
		);
	}

	private static GenericDeclarationList toGenerics(Set<TargetGeneric> generics) {
		return new GenericDeclarationList(
			generics.stream().map(g
					-> new GenericTypeVar(g.name(), List.of(toRefType(g.bound())), new NullToken(), new NullToken())
			).toList(), new NullToken()
		);
	}

	/**
	 * This writes back the compiled target structure into the loadedClasses map so that it can be used
	 * as an input in further processing
	 * @param target
	 */
	private void writeBackToClass(TargetStructure target) {
		var fielddecl = new ArrayList<Field>();
        for (var field : target.fields()) {
            fielddecl.add(new Field(field.name(), toRefType(field.type()), field.access(), new NullToken()));
        }

		var methods = new ArrayList<Method>();
		for (var method : target.methods()) {
			methods.add(new Method(method.access(), method.name(), toRefType(method.signature().returnType()),
				toParameterList(method.signature().parameters()), new Block(new ArrayList<>(), new NullToken()), toGenerics(method.signature().generics()), new NullToken())
			);
		}

		var constructors = new ArrayList<Constructor>();
		for (var ctor : target.constructors()) {
			constructors.add(new Constructor(ctor.access(), target.getName(), new RefType(target.qualifiedName(), new NullToken()),
				toParameterList(ctor.parameters()), new Block(new ArrayList<>(), new NullToken()), toGenerics(ctor.generics()), new NullToken())
			);
		}

		var generics = new GenericDeclarationList(toGenerics(target.generics()), new NullToken());
		var superClass = (RefType) toRefType(target.superType());
		var isInterface = target instanceof TargetInterface;
		var isFunctionalInterface = false; // TODO We might actually want to generate those
		var implementedInterfaces = target.implementingInterfaces().stream()
			.map(t -> (RefType) toRefType(t)).toList();
		List<RefType> permittedSubtypes = null;
		if (target instanceof TargetInterface tinf && tinf.permittedSubtypes() != null) {
			permittedSubtypes = tinf.permittedSubtypes().stream().map(t -> (RefType) toRefType(t)).toList();
		}

		var clazz = new ClassOrInterface(
			target.modifiers(), target.qualifiedName(),
			fielddecl, Optional.empty(), Optional.empty(),
			methods, constructors, generics, superClass,
			isInterface, isFunctionalInterface, implementedInterfaces,
			permittedSubtypes, new NullToken(), ""
		);
		loadedClasses.put(target.qualifiedName(), clazz);
	}

	public synchronized Map<JavaClassName, byte[]> generateBytecode(SourceFile sf, List<ResultSet> typeInferenceResult) {
		this.generatedWarnings.clear();
		var converter = new ASTToTargetAST(this, typeInferenceResult, sf, classLoader);
		var generatedClasses = new HashMap<JavaClassName, byte[]>();
		for (var clazz : sf.getClasses()) {
			var target = converter.convert(clazz);
			writeBackToClass(target);
			var codegen = new Codegen(target, this, converter);
			var code = codegen.generate();
			generatedClasses.put(clazz.getClassName(), code);
		}
		generatedGenerics.put(sf, converter.javaGenerics());
		converter.generateFunNTypes();
		auxiliaries.forEach((name, source) -> {
			generatedClasses.put(new JavaClassName(name), source);
		});
		return generatedClasses;
	}

	public void writeClassFile(Map<JavaClassName, byte[]> classFiles, File file) throws IOException {
		writeClassFile(classFiles, outputPath == null ? file.getParentFile() : outputPath, outputPath == null);
	}

	public synchronized void writeClassFile(Map<JavaClassName, byte[]> classFiles, File path, boolean preserveHierarchy) throws IOException {
		FileOutputStream output;
		for (JavaClassName name : classFiles.keySet()) {
			byte[] bytecode = classFiles.get(name);
			defaultLogger.info("generating " + name + ".class file ...");
			var subPath = preserveHierarchy ? path : Path.of(path.toString(), name.getPackageName().split("\\.")).toFile();
			File outputFile = new File(subPath, name.getClassName() + ".class");
			outputFile.getAbsoluteFile().getParentFile().mkdirs();
			defaultLogger.info(outputFile.toString());
			output = new FileOutputStream(outputFile);
			output.write(bytecode);
			output.close();
			defaultLogger.success(name + ".class file generated");
		}
	}

	/* PL 2020-03-17 mit TypeExchanger in FCGenerator.java zusammenfuehren */
	/**
	 * Tauscht die GTVs in einem Typ gegen die entsprechenden Typen in der übergebenen Map aus.
	 */
	private static class TypeExchanger implements TypeVisitor<RefTypeOrTPHOrWildcardOrGeneric> {

		private final HashMap<String, RefTypeOrTPHOrWildcardOrGeneric> gtvs;

		TypeExchanger(HashMap<String, RefTypeOrTPHOrWildcardOrGeneric> gtvs) {
			this.gtvs = gtvs;
		}

		@Override
		public RefTypeOrTPHOrWildcardOrGeneric visit(RefType refType) {
			List<RefTypeOrTPHOrWildcardOrGeneric> params = new ArrayList<>();
			for (RefTypeOrTPHOrWildcardOrGeneric param : refType.getParaList()) {
				params.add(param.acceptTV(this));
			}
			RefTypeOrTPHOrWildcardOrGeneric ret = new RefType(refType.getName(), params, new NullToken());
			return ret;
		}

		@Override
		public RefTypeOrTPHOrWildcardOrGeneric visit(SuperWildcardType superWildcardType) {
			SuperWildcardType ret = new SuperWildcardType(superWildcardType.getInnerType().acceptTV(this), superWildcardType.getOffset());
			return ret;
		}

		@Override
		public RefTypeOrTPHOrWildcardOrGeneric visit(TypePlaceholder typePlaceholder) {
			return typePlaceholder; // TypePlaceholder der vererbert wird kann bei der Vererbung nicht instanziert
									// werden.
		}

		@Override
		public RefTypeOrTPHOrWildcardOrGeneric visit(ExtendsWildcardType extendsWildcardType) {
			ExtendsWildcardType ret = new ExtendsWildcardType(extendsWildcardType.getInnerType().acceptTV(this), extendsWildcardType.getOffset());
			return ret;
		}

		@Override
		public RefTypeOrTPHOrWildcardOrGeneric visit(GenericRefType genericRefType) {
			if (!gtvs.containsKey(genericRefType.getParsedName()))
				throw new DebugException("Dieser Fall darf nicht auftreten");
			return gtvs.get(genericRefType.getParsedName());
		}

	}

}
