Skip to content

Instantly share code, notes, and snippets.

@siviae
Created April 24, 2014 08:36
Show Gist options
  • Save siviae/11246664 to your computer and use it in GitHub Desktop.
Save siviae/11246664 to your computer and use it in GitHub Desktop.
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