gt:                 result = Class.forName(className, false, classLoader);
context: /*
   * Copyright (C) 2015 Raquel Pau and Albert Coroleu.
   * 
   * Walkmod is free software: you can redistribute it and/or modify it under the terms of the GNU
   * Lesser General Public License as published by the Free Software Foundation, either version 3 of
   * the License, or (at your option) any later version.
   * 
   * Walkmod is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
   * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
   * General Public License for more details.
   * 
   * You should have received a copy of the GNU Lesser General Public License along with Walkmod. If
   * not, see <http://www.gnu.org/licenses/>.
   */
  package org.walkmod.javalang.compiler.types;
  
  import java.io.File;
  import java.io.IOException;
  import java.lang.reflect.Modifier;
  import java.net.URL;
  import java.net.URLClassLoader;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.jar.JarEntry;
  import java.util.jar.JarFile;
  
  import org.walkmod.javalang.ast.CompilationUnit;
  import org.walkmod.javalang.ast.ImportDeclaration;
  import org.walkmod.javalang.ast.Node;
  import org.walkmod.javalang.ast.SymbolDataAware;
  import org.walkmod.javalang.ast.SymbolDefinition;
  import org.walkmod.javalang.ast.body.AnnotationDeclaration;
  import org.walkmod.javalang.ast.body.BodyDeclaration;
  import org.walkmod.javalang.ast.body.ClassOrInterfaceDeclaration;
  import org.walkmod.javalang.ast.body.EnumDeclaration;
  import org.walkmod.javalang.ast.body.TypeDeclaration;
  import org.walkmod.javalang.ast.expr.ObjectCreationExpr;
  import org.walkmod.javalang.ast.expr.QualifiedNameExpr;
  import org.walkmod.javalang.ast.type.Type;
  import org.walkmod.javalang.compiler.actions.LoadStaticImportsAction;
  import org.walkmod.javalang.compiler.providers.SymbolActionProvider;
  import org.walkmod.javalang.compiler.symbols.ASTSymbolTypeResolver;
  import org.walkmod.javalang.compiler.symbols.ReferenceType;
  import org.walkmod.javalang.compiler.symbols.Scope;
  import org.walkmod.javalang.compiler.symbols.Symbol;
  import org.walkmod.javalang.compiler.symbols.SymbolAction;
  import org.walkmod.javalang.compiler.symbols.SymbolTable;
  import org.walkmod.javalang.compiler.symbols.SymbolType;
  import org.walkmod.javalang.visitors.VoidVisitorAdapter;
  
  /**
   * Inserts into the symbol table the set of types that can be used from an specific class. These
   * types can be resolved through the package, import declarations or inner classes.
   * 
   * Each type is loaded into the symbol table for all possible simple names that can solve this type.
   * 
   * 
   * @author rpau
   *
   */
  public class TypesLoaderVisitor<T> extends VoidVisitorAdapter<T> {
  
      private enum FileFlag {
          Directory, Readable;
  
          private final int mask;
  
          FileFlag() {
              this.mask = 1 << ordinal();
          }
  
          public boolean isSetIn(int flags) {
              return (mask & flags) != 0;
          }
      }
  
      private String contextName = null;
  
      private String packageName = null;
  
      private static SymbolTypesClassLoader classLoader =
              new SymbolTypesClassLoader(Thread.currentThread().getContextClassLoader());
  
      private static Set<String> defaultJavaLangClasses = new HashSet<String>();
  
      private static Map<String, Class<?>> primitiveClasses = new HashMap<String, Class<?>>();
  
      private static Map<String, List<String>> jarFileClassNames = new HashMap<String, List<String>>();
  
      /** conceptually a Map<File.absoluteFilePath,EnumSet<FileFlag>> */
      private static Map<String, Integer> fileFlagCache = new HashMap<String, Integer>();
  
      private static JarFile SDKJar;
  
      private SymbolTable symbolTable = null;
  
      private List<SymbolAction> actions;
  
      private SymbolActionProvider actionProvider = null;
  
      private Node startingNode = null;
  
      public TypesLoaderVisitor(SymbolTable symbolTable, SymbolActionProvider actionProvider,
              List<SymbolAction> actions) {
          this.symbolTable = symbolTable;
          this.actions = actions;
          this.actionProvider = actionProvider;
          for (String defaultType : primitiveClasses.keySet()) {
              SymbolType st = new SymbolType(primitiveClasses.get(defaultType));
              symbolTable.pushSymbol(defaultType, ReferenceType.TYPE, st, null, actions);
  
          }
          for (String defaultType : defaultJavaLangClasses) {
  
              SymbolType st = new SymbolType(defaultType);
              symbolTable.pushSymbol(resolveSymbolName(defaultType, false, false), ReferenceType.TYPE, st, null, actions);
          }
      }
  
      /**
      * Infers the simple name to be pushed into the symbol table
      * 
      * @param name
      *           full name of a given class
      * @param imported
      *           if it appears as an import declaration. It is important, because the simple name is
      *           the part after the $ symbol if it is an inner class.
      * @param importedInner
      *           resolve name of nested inner class of import
      * @return the simple name to be pushed into the symbol table
      */
      //@VisibleForTesting
      static String resolveSymbolName(final String name, final boolean imported, final boolean importedInner) {
          final int dot = name.lastIndexOf(".");
          String simpleName = dot != -1 ? name.substring(dot + 1) : name;
  
          if (importedInner) {
              simpleName = simpleName.replace("$", ".");
          } else if (imported) {
              final int dollar = simpleName.lastIndexOf("$");
              if (dollar != -1) {
                  simpleName = simpleName.substring(dollar + 1);
              }
          } else {
              final String[] splittedString = simpleName.split("\\$\\d");
              final String aux = splittedString[splittedString.length - 1];
  
              if (!aux.equals("")) {
                  if (aux.charAt(0) == '$') {
                      simpleName = aux.substring(1);
                  } else {
                      simpleName = aux;
                  }
              }
              simpleName = simpleName.replace('$', '.');
          }
          return simpleName;
      }
  
      public void setClassLoader(ClassLoader cl) {
          classLoader = new SymbolTypesClassLoader(cl);
      }
  
      public static SymbolTypesClassLoader getClassLoader() {
          return classLoader;
      }
  
      private SymbolType buildSymbolType(TypeDeclaration type) {
  
          SymbolType st = (SymbolType) type.getSymbolData();
          if (st != null) {
              return st;
          }
          String name = type.getName();
          Node node = type.getParentNode();
  
          if (node instanceof SymbolDataAware<?>) {
              st = (SymbolType) ((SymbolDataAware<?>) node).getSymbolData();
  
          }
          if (st != null) {
              name = st.getName() + "$" + name;
          } else {
              if (contextName != null && !contextName.equals("")) {
                  if (packageName != null && packageName.equals(contextName)) {
                      name = contextName + "." + name;
                  } else {
                      name = contextName + "$" + name;
                  }
              }
          }
          st = new SymbolType(name);
          type.setSymbolData(st);
          return st;
      }
  
      private void pushCanonicalName(Symbol<?> added, SymbolType st) {
          String fullName = st.getName().replaceAll("\\$", ".");
          Symbol<?> importedPkgSymbol = symbolTable.findSymbol(fullName, ReferenceType.TYPE);
          if (importedPkgSymbol == null) {
              Symbol<?> aux = symbolTable.pushSymbol(fullName, ReferenceType.TYPE, added.getType(), added.getLocation());
              aux.setInnerScope(added.getInnerScope());
          } else if (importedPkgSymbol.getLocation() == null) {
              importedPkgSymbol.setInnerScope(added.getInnerScope());
          }
      }
  
      private <K extends Node & SymbolDefinition> void overrideSymbol(Symbol<K> symbol, K location, SymbolType st,
              Scope scope) {
          symbol.setLocation(location);
          symbol.setInnerScope(scope);
          symbol.setType(st);
      }
  
      private String getInnerName(SymbolType st) {
          String preffix = ((SymbolDataAware<?>) startingNode).getSymbolData().getName();
          return resolveSymbolName(st.getName().substring(preffix.length() + 1), false, false);
      }
  
      @SuppressWarnings({"rawtypes", "unchecked"})
      private String getContext(TypeDeclaration type, T context) {
  
          List<SymbolAction> actions = new LinkedList<SymbolAction>();
  
          if (actionProvider != null) {
              actions.addAll(actionProvider.getActions(type));
          }
  
          SymbolType st = buildSymbolType(type);
          String keyName = resolveSymbolName(st.getName(), false, false);
  
          Symbol oldSymbol = symbolTable.findSymbol(keyName, ReferenceType.TYPE);
          Symbol added = null;
          if (oldSymbol == null || !oldSymbol.getType().equals(st)) {
              added = new Symbol(keyName, st, type, ReferenceType.TYPE, false, actions);
              final Scope addedScope = new Scope(added);
              added.setInnerScope(addedScope);
  
              addPreliminaryThis(addedScope, added, type);
  
              symbolTable.pushSymbol(added, true);
          } else {
  
              Node location = oldSymbol.getLocation();
              if ((location == null || location instanceof ImportDeclaration) && !st.belongsToAnonymousClass()) {
                  added = oldSymbol;
                  final Scope addedScope = new Scope(added);
                  overrideSymbol(added, type, st, addedScope);
  
                  addPreliminaryThis(addedScope, added, type);
              } else {
                  if (startingNode != null && startingNode != type) {
  
                      String innerClassName = getInnerName(st);
                      added = new Symbol(innerClassName, st, type, ReferenceType.TYPE, false, actions);
                      added.setInnerScope(oldSymbol.getInnerScope());
                      symbolTable.pushSymbol(added, true);
  
                  }
              }
  
          }
          if (added != null && !st.belongsToAnonymousClass()) {
              pushCanonicalName(added, st);
          }
          return st.getName();
      }
  
      /** preliminary "this" to allow depth first inheritance tree scope loading */
      private void addPreliminaryThis(Scope scope, Symbol symbol, TypeDeclaration type) {
          Symbol<TypeDeclaration> thisSymbol =
                  new Symbol<TypeDeclaration>("this", symbol.getType(), type, ReferenceType.VARIABLE);
          thisSymbol.setInnerScope(scope);
          scope.addSymbol(thisSymbol);
      }
  
      public void visit(ClassOrInterfaceDeclaration type, T context) {
          boolean restore = false;
          if (startingNode == null) {
              startingNode = type;
              restore = true;
          }
          String name = getContext(type, context);
          String oldCtx = contextName;
          contextName = name;
          List<BodyDeclaration> members = type.getMembers();
          processMembers(members, context);
          contextName = oldCtx;
          if (restore) {
              startingNode = null;
          }
      }
  
      public void visit(EnumDeclaration type, T context) {
          boolean restore = false;
          if (startingNode == null) {
              startingNode = type;
              restore = true;
          }
          String name = getContext(type, context);
          String oldCtx = contextName;
          contextName = name;
          List<BodyDeclaration> members = type.getMembers();
          processMembers(members, context);
          contextName = oldCtx;
          if (restore) {
              startingNode = null;
          }
      }
  
      public void visit(AnnotationDeclaration type, T context) {
          boolean restore = false;
          if (startingNode == null) {
              startingNode = type;
              restore = true;
          }
          String name = getContext(type, context);
          String oldCtx = contextName;
          contextName = name;
          List<BodyDeclaration> members = type.getMembers();
          processMembers(members, context);
          contextName = oldCtx;
          if (restore) {
              startingNode = null;
          }
      }
  
      public void processMembers(List<BodyDeclaration> members, T context) {
          if (members != null) {
              for (BodyDeclaration bd : members) {
                  if (bd instanceof TypeDeclaration) {
                      bd.accept(this, context);
                  }
              }
          }
      }
  
      public void visit(ObjectCreationExpr n, T context) {
          boolean restore = false;
          if (startingNode == null) {
              startingNode = n;
              restore = true;
          }
          List<BodyDeclaration> members = n.getAnonymousClassBody();
          if (members != null) {
              SymbolType st = symbolTable.getType("this", ReferenceType.VARIABLE);
              n.setSymbolData(st);
              String name = st.getName();
              String oldCtx = contextName;
              contextName = name;
              processMembers(members, context);
              contextName = oldCtx;
          }
          if (restore) {
              startingNode = null;
          }
      }
  
      private Class<?> lookForClass(String className) {
          Class<?> result = null;
          boolean finish = false;
          while (result == null && !finish) {
              try {
  