Created
April 24, 2014 08:36
-
-
Save siviae/11246664 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ru.ifmo.ctddev.isaev; | |
import info.kgeorgiy.java.advanced.implementor.Impler; | |
import info.kgeorgiy.java.advanced.implementor.ImplerException; | |
import info.kgeorgiy.java.advanced.implementor.JarImpler; | |
import javax.tools.JavaCompiler; | |
import javax.tools.ToolProvider; | |
import java.io.*; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Modifier; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.jar.Attributes; | |
import java.util.jar.JarOutputStream; | |
import java.util.jar.Manifest; | |
import java.util.zip.ZipEntry; | |
public class Implementor implements Impler, JarImpler { | |
/** | |
* String representation of implemented class name | |
*/ | |
private String parentClassName; | |
/** | |
* String representation of resulting class name | |
*/ | |
private String className; | |
/** | |
* System line separator | |
*/ | |
public static final String LS = System.getProperty("line.separator"); | |
/** | |
* System file separator | |
*/ | |
public static final String FS = System.getProperty("file.separator"); | |
/** | |
* System path separator | |
*/ | |
private static final String PS = System.getProperty("path.separator"); | |
/** | |
* Main {@link java.lang.StringBuilder} object, containing implementation | |
*/ | |
private StringBuilder sb; | |
/** | |
* Boolean value, true if given type-token is interface | |
*/ | |
boolean isInterface; | |
/** | |
* True, if we implemented default counstructor in our class | |
*/ | |
boolean implementedDefaultConstructor; | |
/** | |
* Array of ancestor`s constructors | |
*/ | |
private Constructor<?>[] constructors; | |
/** | |
* All imported packages | |
*/ | |
private Set<Package> packages = new HashSet<>(); | |
/** | |
* All methods in all ancestors | |
*/ | |
private Map<String, Method> allMethods = new HashMap<>(); | |
/** | |
* Only methods, thad should be implemented | |
*/ | |
private Map<String, Method> methods = new HashMap<>(); | |
/** | |
* Parent class package | |
*/ | |
private String pack; | |
/** | |
* File descriptor of file with implementation | |
*/ | |
private File targetFile; | |
private String modifiers(int m) { | |
return Modifier.toString(m); | |
} | |
/** | |
* Implements constructor with no parameters | |
* | |
* @return {@link java.lang.StringBuilder} with implementation | |
* | |
* @throws ImplerException | |
*/ | |
private StringBuilder generateDefaultConstructor() throws ImplerException { | |
StringBuilder sb = new StringBuilder(); | |
sb.append("public ").append(className).append("() "); | |
if (constructors.length > 0) { | |
sb.append(getThrowsPart(constructors[0].getExceptionTypes())); | |
} | |
sb.append(" {").append(LS); | |
sb.append(callSuperConstructor()).append("}").append(LS + LS); | |
return sb; | |
} | |
/** | |
* Adds superclass constructor calling to implementation | |
* | |
* @return {@link java.lang.StringBuilder} with implementation | |
* | |
* @throws ImplerException if there is no accessible constructor in superclass or interface | |
*/ | |
private StringBuilder callSuperConstructor() throws ImplerException { | |
StringBuilder sb = new StringBuilder(); | |
if (constructors.length > 0) { | |
boolean f = false; | |
Constructor<?> parentCons = null; | |
for (Constructor<?> cons : constructors) { | |
if (!Modifier.isPrivate(cons.getModifiers())) { | |
parentCons = cons; | |
f = true; | |
break; | |
} | |
} | |
if (!f) { | |
throw new ImplerException("Cannot call super constructor"); | |
} | |
Class<?>[] consParams = parentCons.getParameterTypes(); | |
sb.append("super("); | |
for (int i = 0; i < consParams.length; i++) { | |
sb.append("(").append(getValidType(consParams[i])).append(") ").append(getDefaultValue(consParams[i])); | |
if (i != consParams.length - 1) { | |
sb.append(", "); | |
} | |
} | |
sb.append(");"); | |
} | |
return sb; | |
} | |
/** | |
* Implements constructor by {@link java.lang.reflect.Constructor} object | |
* | |
* @param constructor | |
* | |
* @return {@link java.lang.StringBuilder} with implementation | |
* | |
* @throws ImplerException if there is no accessible constructor in superclass or interface | |
*/ | |
private StringBuilder implementConstructor(Constructor<?> constructor) throws ImplerException { | |
StringBuilder sb = new StringBuilder(); | |
sb.append(modifiers(constructor.getModifiers()).replace("transient", " ")).append(" "); | |
sb.append(className).append("("); | |
Class<?>[] params = constructor.getParameterTypes(); | |
for (int i = 0; i < params.length; i++) { | |
Class<?> param = params[i]; | |
sb.append(getValidType(param)).append(" ").append("p").append(i); | |
if (i != params.length - 1) { | |
sb.append(", "); | |
} | |
} | |
sb.append(") "); | |
sb.append(getThrowsPart(constructor.getExceptionTypes())); | |
sb.append(" {").append(LS); | |
sb.append(callSuperConstructor()); | |
sb.append(LS).append("}").append(LS + LS); | |
return sb; | |
} | |
/** | |
* Returns package string by {@link java.lang.Package} or empty string if parameter is null | |
* | |
* @param aPackage | |
* | |
* @return {@link java.lang.String} with package representation | |
*/ | |
private String getValidPackageString(Package aPackage) { | |
return (aPackage == null ? "" : aPackage.getName() + "."); | |
} | |
/** | |
* @param clazz | |
* | |
* @return {@link java.lang.String} representing valid type by given {@link java.lang.Class} object | |
*/ | |
private String getValidType(Class<?> clazz) { | |
return getValidPackageString(clazz.getPackage()) + clazz.getSimpleName(); | |
} | |
/** | |
* Contains enum with default values of all existing primitives and null for object | |
* | |
* @param clazz | |
* | |
* @return {@link java.lang.String} with default value | |
*/ | |
private String getDefaultValue(Class<?> clazz) { | |
switch (clazz.getSimpleName()) { | |
case "byte": | |
return " 0 "; | |
case "short": | |
return " 0 "; | |
case "int": | |
return " 0 "; | |
case "long": | |
return " 0L "; | |
case "float": | |
return " 0.0f "; | |
case "double": | |
return " 0.0d "; | |
case "char": | |
return " \u0000 "; | |
case "boolean": | |
return " false "; | |
case "void": | |
return " "; | |
default: | |
return " null "; | |
} | |
} | |
/** | |
* Generates valid throws declarations by array of exception classes | |
* | |
* @param exceptions | |
* | |
* @return {@link java.lang.StringBuilder} with throws declaration | |
*/ | |
private StringBuilder getThrowsPart(Class<?>[] exceptions) { | |
StringBuilder sb = new StringBuilder(); | |
if (exceptions.length != 0) { | |
sb.append(" throws "); | |
for (int i = 0; i < exceptions.length; i++) { | |
Class<?> e = exceptions[i]; | |
sb.append(e.getName()); | |
if (i != exceptions.length - 1) { | |
sb.append(", "); | |
} | |
} | |
} | |
return sb; | |
} | |
/** | |
* Implements metod by given {@link java.lang.reflect.Method} instance. Implemented method will do nothing but return | |
* default value of their return type | |
* | |
* @param method | |
* | |
* @return {@link java.lang.StringBuilder} with method implementation | |
*/ | |
private StringBuilder implementMethod(Method method) { | |
StringBuilder sb = new StringBuilder(); | |
sb.append("@Override").append(LS); | |
sb.append(modifiers(method.getModifiers() & ~Modifier.ABSTRACT).replace("abstract", "").replace("transient", "")).append(" "); | |
sb.append(getValidType(method.getReturnType())).append(" "); | |
sb.append(method.getName()).append("("); | |
Class<?>[] params = method.getParameterTypes(); | |
for (int i = 0; i < params.length; i++) { | |
Class<?> param = params[i]; | |
sb.append(getValidType(param)).append(" ").append("p").append(i); | |
if (i != params.length - 1) { | |
sb.append(", "); | |
} | |
} | |
sb.append(")"); | |
sb.append(getThrowsPart(method.getExceptionTypes())); | |
sb.append("{ ").append(LS).append(getReturnSection(method.getReturnType())).append(LS); | |
sb.append("}").append(LS); | |
return sb; | |
} | |
/** | |
* Generates return section for method by given type | |
* | |
* @param returnType | |
* | |
* @return {@link java.lang.StringBuilder} with default value of return type | |
*/ | |
private StringBuilder getReturnSection(Class<?> returnType) { | |
StringBuilder sb = new StringBuilder(); | |
String s = getDefaultValue(returnType); | |
if (!s.equals(" ")) { | |
sb.append("return ").append(s).append(";"); | |
} | |
return sb; | |
} | |
/** | |
* Generates signature of given {@link java.lang.reflect.Method} - some kind of method`s unique ID | |
* | |
* @param method | |
* | |
* @return generated signature | |
*/ | |
private String getMethodSignature(Method method) { | |
StringBuilder sb = new StringBuilder(); | |
sb.append(method.getName()).append("("); | |
Class<?>[] params = method.getParameterTypes(); | |
for (int i = 0; i < params.length; i++) { | |
Class<?> param = params[i]; | |
sb.append(param.getName()); | |
if (i != params.length - 1) { | |
sb.append(", "); | |
} | |
} | |
sb.append(")"); | |
return sb.toString(); | |
} | |
/** | |
* Recursive method, visit class hierarchy from starting class to {@link java.lang.Object} exclusive, | |
* collecting all methods, that must be implemented. | |
* | |
* @param clazz starting point | |
*/ | |
private void getMethods(Class<?> clazz) { | |
if (clazz == Object.class) | |
return; | |
for (Method method : clazz.getDeclaredMethods()) { | |
int mod = method.getModifiers(); | |
if ((!Modifier.isPrivate(mod))) { | |
if (Modifier.isAbstract(mod)) { | |
Method m = allMethods.get(method.getName()); | |
if ((m == null || !Modifier.isAbstract(mod)) && !Modifier.isFinal(mod)) { | |
String s = getMethodSignature(method); | |
if (!methods.containsKey(s)) { | |
methods.put(s, method); | |
} | |
} | |
} else { | |
allMethods.put(method.getName(), method); | |
} | |
} | |
} | |
if (clazz.getSuperclass() != null) getMethods(clazz.getSuperclass()); | |
for (Class<?> cl : clazz.getInterfaces()) { | |
getMethods(cl); | |
} | |
} | |
/** | |
* Initializes all fields required to correct work | |
*/ | |
private void init(Class<?> clazz, File root) throws ImplerException { | |
sb = new StringBuilder(); | |
methods.clear(); | |
allMethods.clear(); | |
packages.clear(); | |
constructors = null; | |
isInterface = false; | |
implementedDefaultConstructor = false; | |
pack = clazz.getPackage() == null ? "" : clazz.getPackage().getName(); | |
targetFile = new File(root.getPath() + FS + pack.replace(".", FS) + FS + clazz.getSimpleName() + "Impl.java"); | |
File parent = targetFile.getParentFile(); | |
if (!parent.exists() && !parent.mkdirs()) { | |
throw new ImplerException("Couldn't create dir: " + parent); | |
} | |
} | |
/** | |
* Implements class and save the implementation to {@link #sb} | |
* | |
* @param clazz | |
* | |
* @throws ImplerException | |
*/ | |
private void implement(Class<?> clazz) throws ImplerException { | |
if (Modifier.isFinal(clazz.getModifiers())) { | |
throw new ImplerException("Cannot implement final class!"); | |
} | |
parentClassName = clazz.getSimpleName(); | |
className = parentClassName + "Impl"; | |
if (!pack.isEmpty()) { | |
sb.append("package ").append(pack).append(PS).append(LS); | |
} | |
constructors = clazz.getDeclaredConstructors(); | |
getMethods(clazz); | |
for (Method method : methods.values()) { | |
Class<?>[] params = method.getParameterTypes(); | |
for (int i = 0; i < params.length; i++) { | |
packages.add(params[i].getPackage()); | |
} | |
} | |
for (Constructor<?> constructor : constructors) { | |
Class<?>[] params = constructor.getParameterTypes(); | |
for (int i = 0; i < params.length; i++) { | |
packages.add(params[i].getPackage()); | |
} | |
} | |
for (Package p : packages) { | |
if (p != null) { | |
sb.append("import " + p.getName() + ".*;" + LS); | |
} | |
} | |
sb.append("public class "); | |
isInterface = clazz.isInterface(); | |
sb.append(className).append(" "); | |
sb.append(isInterface ? "implements " : "extends ").append(clazz.getName()).append(" {").append(LS).append(LS); | |
for (Constructor<?> constructor : constructors) { | |
if (constructor.getParameterTypes().length == 0) { | |
implementedDefaultConstructor = true; | |
} | |
sb.append(implementConstructor(constructor)); | |
} | |
if (!implementedDefaultConstructor) { | |
sb.append(generateDefaultConstructor()); | |
} | |
for (Method m : methods.values()) { | |
sb.append(implementMethod(m)); | |
} | |
sb.append(LS).append("}"); | |
} | |
/** | |
* Implements or extends given interface/class and name it like ParentNameImpl.java | |
* | |
* @param clazz | |
* @param root root directory. | |
* | |
* @throws ImplerException | |
*/ | |
@Override | |
public void implement(Class<?> clazz, File root) throws ImplerException { | |
init(clazz, root); | |
implement(clazz); | |
try (PrintWriter out = new PrintWriter(targetFile)) { | |
out.print(sb); | |
} catch (FileNotFoundException e) { | |
System.out.println("File not found"); | |
} | |
} | |
/** | |
* Recursively deletes directory by given file descriptor | |
* | |
* @param directory | |
*/ | |
void deleteDir(File directory) { | |
if (directory.isDirectory()) { | |
for (File c : directory.listFiles()) | |
deleteDir(c); | |
directory.delete(); | |
} else { | |
if (!directory.delete()) { | |
System.out.println("Failed to delete directory: " + directory); | |
} | |
} | |
} | |
/** | |
* Implements class by given type-token and put it to .jar file | |
* | |
* @param token type token to create implementation for. | |
* @param jarFile target <tt>.jar</tt> file. | |
* | |
* @throws ImplerException | |
*/ | |
@Override | |
public void implementJar(Class<?> token, File jarFile) throws ImplerException { | |
try { | |
String fileName = "tmp" + FS; | |
File file = new File(fileName); | |
implement(token, file); | |
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); | |
compiler.run(null, null, null, targetFile.getPath()); | |
FileOutputStream out = new FileOutputStream(jarFile); | |
Manifest manifest = new Manifest(); | |
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); | |
//manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, pathToClass); | |
manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, pack + "." + className); | |
JarOutputStream jOut = new JarOutputStream(out, manifest); | |
File compiledClass = new File(targetFile.getPath().replace(".java", ".class")); | |
String temp = compiledClass.getPath().replace("tmp" + FS, ""); | |
jOut.putNextEntry(new ZipEntry(temp)); | |
int bytesRead; | |
byte[] buffer = new byte[8 * 1024]; | |
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(compiledClass)); | |
while ((bytesRead = bis.read(buffer)) != -1) { | |
jOut.write(buffer, 0, bytesRead); | |
} | |
bis.close(); | |
jOut.closeEntry(); | |
jOut.closeEntry(); | |
jOut.close(); | |
out.close(); | |
deleteDir(file); | |
} catch (FileNotFoundException e) { | |
System.out.println("Jar file doesnt exist"); | |
} catch (IOException e) { | |
System.out.println("Cant open jar output stream"); | |
} | |
} | |
/** | |
* Entry point | |
* | |
* @param args | |
*/ | |
public static void main(String[] args) { | |
Implementor impler = new Implementor(); | |
String className = null; | |
try { | |
if (args.length == 2 && args[0].equals("-jar")) { | |
className = args[1]; | |
Class clazz = Class.forName(className); | |
File jarFile = new File("lib" + FS + clazz.getSimpleName() + "Impl.jar"); | |
jarFile.getParentFile().mkdirs(); | |
jarFile.createNewFile(); | |
impler.implementJar(clazz, jarFile); | |
} else { | |
if (args.length == 1) { | |
className = args[0]; | |
Class clazz = Class.forName(className); | |
File file = new File("."); | |
impler.implement(clazz, file); | |
} else { | |
System.out.println("Invalid arguments. Please, set one parameter : class full name"); | |
} | |
} | |
} catch (ClassNotFoundException e) { | |
System.out.print("Cannot find class with name " + args[0] + " "); | |
} catch (ImplerException e) { | |
System.out.println("Cannot generate default implementation for class " + className); | |
} catch (IOException e) { | |
System.out.println("Cannot create jar file"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment