code: package edu.iastate.cs.egroum.aug;
  
  import edu.iastate.cs.egroum.utils.FileIO;
  import edu.iastate.cs.egroum.utils.JavaASTUtil;
  import org.apache.bcel.Constants;
  import org.apache.bcel.classfile.*;
  import org.apache.bcel.generic.Type;
  import org.eclipse.jdt.core.JavaCore;
  import org.eclipse.jdt.core.dom.*;
  
  import java.io.File;
  import java.io.IOException;
  import java.util.*;
  import java.util.jar.JarEntry;
  import java.util.jar.JarFile;
  
  public class EGroumBuilder {
  	private final AUGConfiguration configuration;
  
  	public EGroumBuilder(AUGConfiguration configuration) {
  		this.configuration = configuration;
  	}
  
  	/**
  	 * @param classpaths if you are to parse a batch of files, do not include the
  	 *                   folder of their class files in the class paths because it
  	 *                   is redundant and it will slow the parsing down crazily,
  	 *                   i.e., hundreds of times for large project such as JDT core.
  	 */
  	public ArrayList<EGroumGraph> buildBatch(String path, String[] classpaths) {
  		buildStandardJars();
  		buildHierarchy(new File(path));
  		return buildBatchGroums(new File(path), classpaths);
  	}
  
  	/**
  	 * @param classpaths if you are to parse a batch of files, do not include the
  	 *                   folder of their class files in the class paths because it
  	 *                   is redundant and it will slow the parsing down crazily,
  	 *                   i.e., hundreds of times for large project such as JDT core.
  	 */
  	public ArrayList<EGroumGraph> build(String path, String[] classpaths) {
  		buildStandardJars();
  		buildHierarchy(new File(path));
  		return buildGroums(new File(path), classpaths);
  	}
  
  	private void buildStandardJars() {
  		String jrePath = System.getProperty("java.home") + "/lib";
  		buildJar(jrePath + "/rt.jar");
  	}
  
  	private void buildHierarchy(File file) {
  		if (file.isDirectory()) {
  			for (File sub : file.listFiles())
  				buildHierarchy(sub);
  		} else if (file.isFile()) {
  			if (file.getName().endsWith(".jar"))
  				buildJar(file.getAbsolutePath());
  			else if (file.getName().endsWith(".java")) {
  				try {
  					CompilationUnit cu = (CompilationUnit) JavaASTUtil
  							.parseSource(FileIO.readStringFromFile(file.getAbsolutePath()));
  					for (int i = 0; i < cu.types().size(); i++)
  						buildHierarchy((AbstractTypeDeclaration) cu.types().get(i),
  								cu.getPackage() == null ? "" : cu.getPackage().getName().getFullyQualifiedName() + ".");
  				} catch (Exception e) {
  					System.err
  							.println("Failed to parse file " + file.getAbsolutePath() + ": " + e.getClass().getName());
  					// TODO Suppress runtime problems with unknown reason
  				}
  			}
  		}
  		EGroumBuildingContext.buildExceptionHierarchy();
  	}
  
  	private void buildHierarchy(AbstractTypeDeclaration type, String prefix) {
  		if (type instanceof TypeDeclaration)
  			buildHierarchy((TypeDeclaration) type, prefix);
  		else if (type instanceof EnumDeclaration)
  			buildHierarchy((EnumDeclaration) type, prefix);
  		else if (type instanceof AnnotationTypeDeclaration)
  			buildHierarchy((AnnotationTypeDeclaration) type, prefix);
  	}
  
  	private void buildHierarchy(TypeDeclaration type, String prefix) {
  		String className = prefix + type.getName().getIdentifier();
  		if (type.getSuperclassType() != null) {
  			String stype = JavaASTUtil.getSimpleType(type.getSuperclassType());
  			HashSet<String> subs = EGroumBuildingContext.exceptionHierarchy.get(stype);
  			if (subs == null) {
  				subs = new HashSet<>();
  				EGroumBuildingContext.exceptionHierarchy.put(stype, subs);
  			}
  			subs.add(className);
  		}
  		HashMap<String, String> fieldTypes = EGroumBuildingContext.typeFieldType.get(className);
  		if (fieldTypes == null)
  			fieldTypes = new HashMap<>();
  		for (FieldDeclaration field : type.getFields())
  			buildHierarchy(field, fieldTypes);
  		if (!fieldTypes.isEmpty())
  			EGroumBuildingContext.typeFieldType.put(className, fieldTypes);
  		HashMap<String, HashSet<String>> methodExceptions = EGroumBuildingContext.typeMethodExceptions.get(className);
  		if (methodExceptions == null)
  			methodExceptions = new HashMap<>();
  		for (MethodDeclaration method : type.getMethods())
  			buildHierarchy(method, methodExceptions);
  		if (!methodExceptions.isEmpty())
  			EGroumBuildingContext.typeMethodExceptions.put(className, methodExceptions);
  		for (TypeDeclaration inner : type.getTypes())
  			buildHierarchy(inner, className + ".");
  	}
  
  	private void buildHierarchy(EnumDeclaration ed, String prefix) {
  		String className = prefix + ed.getName().getIdentifier();
  		HashMap<String, String> fieldTypes = EGroumBuildingContext.typeFieldType.get(className);
  		if (fieldTypes == null)
  			fieldTypes = new HashMap<>();
  		HashMap<String, HashSet<String>> methodExceptions = EGroumBuildingContext.typeMethodExceptions.get(className);
  		if (methodExceptions == null)
  			methodExceptions = new HashMap<>();
  		for (int i = 0; i < ed.bodyDeclarations().size(); i++) {
  			BodyDeclaration bd = (BodyDeclaration) ed.bodyDeclarations().get(i);
  			if (bd instanceof FieldDeclaration)
  				buildHierarchy((FieldDeclaration) bd, fieldTypes);
  			else if (bd instanceof MethodDeclaration)
  				buildHierarchy((MethodDeclaration) bd, methodExceptions);
  		}
  		if (!fieldTypes.isEmpty())
  			EGroumBuildingContext.typeFieldType.put(className, fieldTypes);
  		if (!methodExceptions.isEmpty())
  			EGroumBuildingContext.typeMethodExceptions.put(className, methodExceptions);
  	}
  
  	private void buildHierarchy(AnnotationTypeDeclaration type, String prefix) {
  		// TODO
  	}
  
  	private void buildHierarchy(FieldDeclaration f, HashMap<String, String> fieldTypes) {
  		String type = JavaASTUtil.getSimpleType(f.getType());
  		for (int j = 0; j < f.fragments().size(); j++) {
  			VariableDeclarationFragment vdf = (VariableDeclarationFragment) f.fragments().get(j);
  			String dimensions = "";
  			for (int i = 0; i < vdf.getExtraDimensions(); i++)
  				dimensions += "[]";
  			fieldTypes.put(vdf.getName().getIdentifier(), type + dimensions);
  		}
  	}
  
  	private void buildHierarchy(MethodDeclaration method, HashMap<String, HashSet<String>> methodExceptions) {
  		String name = method.getName().getIdentifier();
  		if (method.isConstructor())
  			name = "<init>";
  		name += "(" + method.parameters().size() + ")";
  		HashSet<String> exceptions = methodExceptions.get(name);
  		if (exceptions == null)
  			exceptions = new HashSet<>();
  		for (int i = 0; i < method.thrownExceptionTypes().size(); i++)
  			exceptions.add(
  					JavaASTUtil.getSimpleType((org.eclipse.jdt.core.dom.Type) method.thrownExceptionTypes().get(i)));
  		if (!exceptions.isEmpty())
  			methodExceptions.put(name, exceptions);
  	}
  
  	private void buildJar(String jarFilePath) {
  		try {
  			JarFile jarFile = new JarFile(jarFilePath);
  			Enumeration<JarEntry> entries = jarFile.entries();
  			while (entries.hasMoreElements()) {
  				JarEntry entry = entries.nextElement();
  				if (entry.getName().endsWith(".class") && entry.getName().startsWith("java")) {
  					try {
  						ClassParser parser = new ClassParser(jarFilePath, entry.getName());
  						JavaClass jc = parser.parse();
  						String className = jc.getClassName();
  						className = className.replace('$', '.');
  						HashMap<String, String> fieldTypes = EGroumBuildingContext.typeFieldType.get(className);
  						if (fieldTypes == null)
  							fieldTypes = new HashMap<>();
  						for (Field field : jc.getFields())
  							buildJar(field, fieldTypes);
  						if (!fieldTypes.isEmpty())
  							EGroumBuildingContext.typeFieldType.put(className, fieldTypes);
  						String simpleClassName = className.substring(className.lastIndexOf('.') + 1);
  						HashMap<String, HashSet<String>> methodExceptions = EGroumBuildingContext.typeMethodExceptions
  								.get(simpleClassName);
  						if (methodExceptions == null)
  							methodExceptions = new HashMap<>();
  						for (Method method : jc.getMethods())
  							buildJar(method, methodExceptions);
  						if (!methodExceptions.isEmpty())
  							EGroumBuildingContext.typeMethodExceptions.put(simpleClassName, methodExceptions);
  						if (jc.getSuperclassName() != null) {
  							String stype = FileIO.getSimpleClassName(jc.getSuperclassName());
  							HashSet<String> subs = EGroumBuildingContext.exceptionHierarchy.get(stype);
  							if (subs == null) {
  								subs = new HashSet<>();
  								EGroumBuildingContext.exceptionHierarchy.put(stype, subs);
  							}
  							subs.add(simpleClassName);
  						}
  					} catch (IOException | ClassFormatException e) {
  						System.err.println("Error in parsing class file: " + entry.getName());
  						System.err.println(e.getMessage());
  					}
  				}
  			}
  			jarFile.close();
  		} catch (IOException e) {
  			System.err.println("Error in parsing jar file: " + jarFilePath);
  			// e.printStackTrace();
  			System.err.println(e.getMessage());
  		}
  	}
  
  	private void buildJar(Field field, HashMap<String, String> fieldTypes) {
  		String name = field.getName();
  		if (name.startsWith("this$"))
  			return;
  		String type = getSimpleType(field.getType());
  		fieldTypes.put(name, type);
  	}
  
  	private void buildJar(Method method, HashMap<String, HashSet<String>> methodExceptions) {
  		String name = method.getName();
  		name += "(" + method.getArgumentTypes().length + ")";
  		HashSet<String> exceptions = methodExceptions.get(name);
  		if (exceptions == null)
  			exceptions = new HashSet<>();
  		if (method.getExceptionTable() != null)
  			for (String exception : method.getExceptionTable().getExceptionNames())
  				exceptions.add(exception.substring(exception.lastIndexOf('.') + 1));
  		if (!exceptions.isEmpty())
  			methodExceptions.put(name, exceptions);
  	}
  
  	/*
  	 * Modify com.sun.org.apache.bcel.internal.generic.Type.toString()
  	 */
  	private String getSimpleType(Type type) {
  		return ((type.equals(Type.NULL) || (type.getType() >= Constants.T_UNKNOWN))) ? type.getSignature()
  				: signatureToString(type.getSignature());
  	}
  
  	/*
  	 * Modify com.sun.org.apache.bcel.internal.classfile.Utility.signatureToString(
  	 * signature, false)
  	 */
  	private String signatureToString(String signature) {
  		try {
  			switch (signature.charAt(0)) {
  			case 'B':
  				return "number"; // return "byte";
  			case 'C':
  				return "char";
  			case 'D':
  				return "number"; // return "double";
  			case 'F':
  				return "number"; // return "float";
  			case 'I':
  				return "number"; // return "int";
  			case 'J':
  				return "number"; // return "long";
  
  			case 'L': { // Full class name
  				int index = signature.indexOf(';'); // Look for closing `;'
  
  				if (index < 0)
  					throw new ClassFormatException("Invalid signature: " + signature);
  
  				return compactClassName(signature.substring(1, index));
  			}
  
  			case 'S':
  				return "number"; // return "short";
  			case 'Z':
  				return "boolean";
  
  			case '[': { // Array declaration
  				int n;
  				StringBuffer brackets;
  
  				brackets = new StringBuffer(); // Accumulate []'s
  
  				// Count opening brackets and look for optional size argument
  				for (n = 0; signature.charAt(n) == '['; n++)
  					brackets.append("[]");
  
  				// The rest of the string denotes a `<field_type>'
  				String type = signatureToString(signature.substring(n));
  
  				return type + brackets.toString();
  			}
  
  			case 'V':
  				return "void";
  
  			default:
  				throw new ClassFormatException("Invalid signature: `" + signature + "'");
  			}
  		} catch (StringIndexOutOfBoundsException e) { // Should never occur
  			throw new ClassFormatException("Invalid signature: " + e + ":" + signature);
  		}
  	}
  
  	/*
  	 * Modify
  	 * com.sun.org.apache.bcel.internal.classfile.Utility.compactClassName(long
  	 * class name, false)
  	 */
  	public static final String compactClassName(String className) {
  		int index = className.indexOf('<');
  		if (index > -1)
  			className = className.substring(0, index);
  		className = className.replace('/', '.'); // Is `/' on all systems, even DOS
  		className = className.substring(className.lastIndexOf('.') + 1);
  		return className;
  	}
  
  	private ArrayList<EGroumGraph> buildBatchGroums(File dir, String[] classpaths) {
  		ArrayList<File> files = FileIO.getPaths(dir);
  		String[] paths = new String[files.size()];
  		for (int i = 0; i < files.size(); i++) {
  			paths[i] = files.get(i).getAbsolutePath();
  		}
  		System.out.println("numPaths : " + paths.length + " , dir = " + dir.getPath());
  		HashMap<String, CompilationUnit> cus = new HashMap<>();
  		Set<String> inspected = new HashSet<>();
  		FileASTRequestor r = new FileASTRequestor() {
  			@Override
  			public void acceptAST(String sourceFilePath, CompilationUnit cu) {
  				inspected.add(sourceFilePath);
  				if (configuration.usageExamplePredicate.matches(sourceFilePath, cu))
  					cus.put(sourceFilePath, cu);
  			}
  		};
  		
  		@SuppressWarnings("rawtypes")
  		Map options = JavaCore.getOptions();
  		options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_1_8);
  		options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_1_8);
  		options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
  		ASTParser parser = ASTParser.newParser(AST.JLS8);
  		parser.setCompilerOptions(options);
  		parser.setEnvironment(classpaths == null ? new String[0] : classpaths, new String[] {}, new String[] {}, true);
  		parser.setResolveBindings(true);
  		parser.setBindingsRecovery(true);
  		parser.createASTs(paths, null, new String[0], r, null);
  		ArrayList<EGroumGraph> groums = new ArrayList<>();
  		for (String path : cus.keySet()) {
  			CompilationUnit cu = cus.get(path);
  			for (int i = 0; i < cu.types().size(); i++)
  				if (cu.types().get(i) instanceof TypeDeclaration)
  					groums.addAll(buildGroums((TypeDeclaration) cu.types().get(i), path, ""));
  		}
  		System.out.println("numInspected : " + inspected.size());
  		for (EGroumGraph groum : groums) {
  			groum.setProjectName(dir.getAbsolutePath());
  		}
  		return groums;
  	}
  
  	private ArrayList<EGroumGraph> buildGroums(File file, String[] classpaths) {
  		ArrayList<EGroumGraph> groums = new ArrayList<>();
  		if (file.isDirectory()) {
  			for (File sub : file.listFiles())
  				groums.addAll(buildGroums(sub, classpaths));
  		} else if (file.isFile() && file.getName().endsWith(".java")) {
  			String sourceCode = FileIO.readStringFromFile(file.getAbsolutePath());
  			groums.addAll(buildGroums(sourceCode, file.getAbsolutePath(), file.getName(), classpaths));
  		}
  		return groums;
  	}
  
  	/**
  	 * @param classpaths if you are to parse a batch of files, do not include the
  	 *                   folder of their class files in the class paths because it
  	 *                   is redundant and it will slow the parsing down crazily,
  	 *                   i.e., hundreds of times for large project such as JDT core.
  	 */
  	public ArrayList<EGroumGraph> buildGroums(String sourceCode, String path, String name, String[] classpaths) {
  		ArrayList<EGroumGraph> groums = new ArrayList<>();
  		CompilationUnit cu = (CompilationUnit) JavaASTUtil.parseSource(sourceCode, path, name, classpaths);
  		for (int i = 0; i < cu.types().size(); i++)
  			if (cu.types().get(i) instanceof TypeDeclaration) {
  				System.out.println("buildGroums: found " + ((TypeDeclaration) cu.types().get(i)).getName());
  				groums.addAll(buildGroums((TypeDeclaration) cu.types().get(i), path, ""));
  			}
  		return groums;
  	}
  
  	private ArrayList<EGroumGraph> buildGroums(TypeDeclaration type, String path, String prefix) {
  		ArrayList<EGroumGraph> groums = new ArrayList<>();
  
  
  		// first-author: for our changes, we don't think about this at the method level.
  		// hence if a method matches, we want to grab information from the entire file 
  		boolean typeMatches = false; 
  		for (MethodDeclaration method : type.getMethods()) {
  			if (configuration.usageExamplePredicate.matches(method)) {
  				typeMatches = true;
  				break;
  			}
  		}
  
  		// do the main pass
  		for (MethodDeclaration method : type.getMethods())
  			if (configuration.usageExamplePredicate.matches(method) // either the method uses the API
  					|| (typeMatches && method.isConstructor())) { // or we want the constructor of a type that uses the API somewhere
  
  				ITypeBinding typeBind = type.resolveBinding();
  				ITypeBinding superTypeBind = typeBind.getSuperclass();
  				ITypeBinding[] interfaceBinds = typeBind.getInterfaces();
  
  				
  				
  				EGroumGraph g = buildGroum(method, path, prefix + type.getName().getIdentifier() + ".");
  				if (configuration.usageExamplePredicate.matches(g)) {
  					groums.add(g);
  				}
  			}
  
  		if (typeMatches) {	// we want all the fields
  			System.out.println("Groum 4 field");
  			for (FieldDeclaration f : type.getFields()) {
  				for (int i = 0; i < f.fragments().size(); i++) {
  					VariableDeclarationFragment vdf = (VariableDeclarationFragment) f.fragments().get(i);
  	
  					String dimensions = "";
  					for (int j = 0; j < vdf.getExtraDimensions(); j++)
  						dimensions += "[]";
  	
  	//					groum.join(vdf.getName(), vdf.getInitializer());
  					try {
  						EGroumGraph g = buildGroum(vdf, path, prefix + type.getName().getIdentifier() + ".");
  						groums.add(g);
  					} catch (Exception e) {
  						// but we can't build it for some reason, let's ignore it.
  						e.printStackTrace();
  						System.out.println("COULD NOT build field declaration groum");
  					}
  				}
  			}
  		}
  		
  		for (TypeDeclaration inner : type.getTypes()) {
  			System.out.println("found inner type");
  			groums.addAll(buildGroums(inner, path, prefix + type.getName().getIdentifier() + "."));
  		}
  		return groums;
  	}
  
  	public static Map<String, EGroumGraph> cache = new HashMap<>();
  	
  	EGroumGraph buildGroum(MethodDeclaration method, String filepath, String name) {
  		String identifier = method.toString() + filepath + name;
  		if (cache.containsKey(identifier)) {
  			return cache.get(identifier);
  		} else {
  			if (cache.size() > 10) {
  				cache.clear();
  			}
  		
  			String sig = JavaASTUtil.buildSignature(method);
  			System.out.println("EGroumBuilder buildGroum:" + filepath + " " + name + sig);
  			EGroumGraph g = new EGroumGraph(method, new EGroumBuildingContext(false), configuration);
  			g.setFilePath(filepath);
  			g.setName(name + sig);
  			
  			cache.put(identifier, g);
  			return g;
  		}
  	}
  	
  	EGroumGraph buildGroum(VariableDeclarationFragment fragment, String filepath, String name) {
  		String sig = fragment.getName().getIdentifier() + "#" + "__FieldOfClass__";
  		String identifier = sig + filepath + name;
  		if (cache.containsKey(identifier)) {
  			return cache.get(identifier);
  		} else {
  			if (cache.size() > 10) {
  				cache.clear();
  			}
  		
  			System.out.println("EGroumBuilder buildGroum for fragment:" + filepath + " " + name + sig);
  			//fragment.getInitializer()
  			EGroumGraph g = new EGroumGraph(fragment, new EGroumBuildingContext(false), configuration);
  			g.setFilePath(filepath);
  			g.setName(name + sig);
  			
  			cache.put(sig, g);
  			return g;
  		}
  	}
  }
  