Advertisement
Guest User

Untitled

a guest
Apr 8th, 2020
181
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 21.58 KB | None | 0 0
  1. package ru.ifmo.rain.brichev.implementor;
  2.  
  3. import info.kgeorgiy.java.advanced.implementor.JarImpler;
  4. import info.kgeorgiy.java.advanced.implementor.ImplerException;
  5.  
  6.  
  7. import javax.tools.JavaCompiler;
  8. import javax.tools.ToolProvider;
  9. import java.io.*;
  10. import java.lang.reflect.*;
  11. import java.net.URISyntaxException;
  12. import java.nio.file.Files;
  13. import java.nio.file.InvalidPathException;
  14. import java.nio.file.Path;
  15. import java.nio.file.Paths;
  16. import java.security.CodeSource;
  17. import java.util.*;
  18. import java.util.function.Function;
  19. import java.util.jar.Attributes;
  20. import java.util.jar.JarOutputStream;
  21. import java.util.jar.Manifest;
  22. import java.util.stream.Collectors;
  23. import java.util.zip.ZipEntry;
  24.  
  25. /**
  26.  * Implementation class for {@link JarImpler} interface.
  27.  */
  28.  
  29. public class JarImplementor implements JarImpler {
  30.     /**
  31.      * Line separator specified to current OS
  32.      */
  33.  
  34.     private static final Character PATH_SEPARATOR = File.separatorChar;
  35.  
  36.     /**
  37.      * End line separator specified to current OS
  38.      */
  39.     private static final String END_LINE = System.lineSeparator();
  40.  
  41.     /**
  42.      * Template of exception message, that is thrown during writing
  43.      */
  44.  
  45.     private static final String WRITING_FILE_ERROR = "Writing file error: ";
  46.  
  47.     /**
  48.      * Whitespace separator.
  49.      */
  50.  
  51.     private static final String TAB = "    ";
  52.  
  53.     /**
  54.      * Default constructor.
  55.      */
  56.  
  57.     public JarImplementor() {
  58.     }
  59.  
  60.     /**
  61.      * Main method.
  62.      * First argument should be "-class" or "-jar".
  63.      * Second argument must be classname and in case of "-jar" option should be name of jar file to generate
  64.      *
  65.      * @param args - arguments
  66.      */
  67.  
  68.     public static void main(String[] args) {
  69.         if (args == null || (args.length != 2 && args.length != 3)) {
  70.             System.out.println("Wrong arguments format");
  71.             return;
  72.         }
  73.  
  74.         for (String arg : args) {
  75.             if (arg == null) {
  76.                 System.out.println("Null argument appeared");
  77.                 return;
  78.             }
  79.         }
  80.  
  81.         try {
  82.             if (args.length == 2) {
  83.                 new ru.ifmo.rain.brichev.implementor.JarImplementor().implement(Class.forName(args[0]), Paths.get(args[1]));
  84.             } else if ("-jar".equals(args[0])) {
  85.                 new ru.ifmo.rain.brichev.implementor.JarImplementor().implementJar(Class.forName(args[1]), Paths.get(args[2]));
  86.             } else {
  87.                 System.out.println("Wrong arguments format");
  88.             }
  89.         } catch (ClassNotFoundException e) {
  90.             System.out.println("Class not found");
  91.         } catch (InvalidPathException e) {
  92.             System.out.println("Invalid path");
  93.         } catch (ImplerException e) {
  94.             System.err.println(e.getMessage());
  95.         }
  96.     }
  97.  
  98.  
  99.     /**
  100.      * Creates an arguments for compiler
  101.      *
  102.      * @param token  class
  103.      * @param tmpDir temporary directory
  104.      * @param cp     path of class
  105.      * @return compile args
  106.      */
  107.     private String[] getArgs(Class<?> token, Path tmpDir, String cp) {
  108.         String[] args = new String[]{
  109.                 "-cp",
  110.                 System.getProperty("java.class.path") + File.pathSeparator
  111.                         + tmpDir + File.pathSeparator +
  112.                         cp,
  113.                 tmpDir.resolve(getFilePath(token, tmpDir, ".java")).toString()};
  114.         return args;
  115.     }
  116.  
  117.     /**
  118.      * Method that clean a temporary directory
  119.      *
  120.      * @param tmpDir temporary directory
  121.      * @throws ImplerException if an error occurs
  122.      */
  123.  
  124.     private void cleanDirectory(Path tmpDir) throws ImplerException {
  125.         try {
  126.             Files.walk(tmpDir)
  127.                     .map(Path::toFile)
  128.                     .sorted(Comparator.reverseOrder())
  129.                     .forEach(File::delete);
  130.         } catch (IOException e) {
  131.             throw new ImplerException("Failed deleting temporary files in " + tmpDir.toString());
  132.         }
  133.     }
  134.  
  135.     /**
  136.      * Convert to unicode string
  137.      *
  138.      * @param in input string
  139.      * @return converted string
  140.      */
  141.     private String toUnicode(String in) {
  142.         StringBuilder b = new StringBuilder();
  143.  
  144.         for (char c : in.toCharArray()) {
  145.             if (c >= 128)
  146.                 b.append("\\u").append(String.format("%04X", (int) c));
  147.             else
  148.                 b.append(c);
  149.         }
  150.  
  151.         return b.toString();
  152.     }
  153.  
  154.  
  155.     /**
  156.      * @param token given token
  157.      * @return path to implementation of class
  158.      */
  159.  
  160.     private static String getPathToImplementation(Class<?> token) {
  161.         return Paths.get("").resolve(token.getPackageName().replace('.', '/')).resolve(token.getSimpleName()).toString().replace('\\', '/');
  162.     }
  163.  
  164.     /**
  165.      * @param token given token
  166.      * @return super class path
  167.      * @throws ImplerException if an error occurs
  168.      */
  169.     private static Path getSuperPath(Class<?> token) throws ImplerException {
  170.         try {
  171.             CodeSource codeSource = token.getProtectionDomain().getCodeSource();
  172.             if (codeSource == null) {
  173.                 return Path.of(".");
  174.             } else {
  175.                 return Path.of(codeSource.getLocation().toURI());
  176.             }
  177.         } catch (InvalidPathException | URISyntaxException e) {
  178.             throw new ImplerException("Classpath generation failed", e);
  179.         }
  180.     }
  181.  
  182.  
  183.     /**
  184.      * @param token     given token
  185.      * @param sourceDir source directory
  186.      * @throws ImplerException if an error occurs
  187.      */
  188.     private static void compile(Class<?> token, Path sourceDir) throws ImplerException {
  189.  
  190.         Path toSuper = getSuperPath(token);
  191.  
  192.         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  193.         if (compiler == null) {
  194.             throw new ImplerException("Compiler is not installed");
  195.         }
  196.  
  197.         String[] compilationArgs = {"-encoding", "UTF-8",
  198.                 "-cp",
  199.                 sourceDir.toString() + File.pathSeparator + toSuper.toString(),
  200.                 Path.of(sourceDir.toString(), getPathToImplementation(token) + "Impl.java").toString()
  201.         };
  202.  
  203.         int compilationResultCode = compiler.run(null, null, null, compilationArgs);
  204.         if (compilationResultCode != 0) {
  205.             throw new ImplerException("Compilation failed (result code is not 0): " + compilationResultCode);
  206.         }
  207.  
  208.     }
  209.  
  210.     /**
  211.      * {@inheritDoc}
  212.      */
  213.  
  214.     @Override
  215.     public void implementJar(Class<?> token, Path jarFile) throws ImplerException {
  216.         if (token == null || jarFile == null) {
  217.             throw new ImplerException("Invalid argument: null");
  218.         }
  219.  
  220.         createDirectories(jarFile);
  221.  
  222.         Path tmpDir;
  223.         try {
  224.             tmpDir = Files.createTempDirectory(jarFile.toAbsolutePath().getParent(), "tmp");
  225.         } catch (IOException e) {
  226.             throw new ImplerException("Error during creating temporary directory", e);
  227.         }
  228.  
  229.         try {
  230.             implement(token, tmpDir);
  231.             compile(token, tmpDir);
  232.  
  233.             Manifest manifest = new Manifest();
  234.             Attributes attributes = manifest.getMainAttributes();
  235.             attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
  236.             attributes.put(Attributes.Name.IMPLEMENTATION_VENDOR, "Anton Brichev");
  237.  
  238.             try (JarOutputStream stream = new JarOutputStream(Files.newOutputStream(jarFile), manifest)) {
  239.                 String implementationPath = getPathToImplementation(token) + "Impl.class";
  240.                 stream.putNextEntry(new ZipEntry(implementationPath));
  241.                 Files.copy(Path.of(tmpDir.toString(), implementationPath), stream);
  242.  
  243.             } catch (IOException e) {
  244.                 throw new ImplerException("Writing a jar file error ", e);
  245.             }
  246.         } finally {
  247.             cleanDirectory(tmpDir);
  248.         }
  249.     }
  250.  
  251.     /**
  252.      * Converts the given token into the path of source file
  253.      * potentially implemented and resolved against the given path
  254.      * and add the given file extension
  255.      *
  256.      * @param token  given token
  257.      * @param root   path to be resolve against
  258.      * @param suffix file extension
  259.      * @return the resulting path
  260.      */
  261.  
  262.     private Path getFilePath(Class<?> token, Path root, String suffix) {
  263.         return root.resolve(token.getPackageName().replace('.', PATH_SEPARATOR))
  264.                 .resolve(token.getSimpleName() + "Impl" + suffix);
  265.     }
  266.  
  267.  
  268.     /**
  269.      * Creates directories using given path
  270.      *
  271.      * @param path given path
  272.      * @throws ImplerException if an error occurs
  273.      */
  274.  
  275.     private void createDirectories(Path path) throws ImplerException {
  276.         if (path.getParent() != null) {
  277.             try {
  278.                 Files.createDirectories(path.getParent());
  279.             } catch (IOException e) {
  280.                 throw new ImplerException("Can't create directories in: " + path);
  281.             }
  282.         }
  283.     }
  284.  
  285.  
  286.     /**
  287.      * Class that pack {@link Method} and provides {@link ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter#equals(Object)} and
  288.      * {@link ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter#hashCode()} depending on packed method
  289.      */
  290.  
  291.     private static class MethodSetter {
  292.  
  293.         /**
  294.          * packed method
  295.          */
  296.  
  297.         final private Method method;
  298.  
  299.  
  300.         /**
  301.          * Constructor receiving method for packing
  302.          *
  303.          * @param method method to be packed
  304.          */
  305.  
  306.         private MethodSetter(Method method) {
  307.             this.method = method;
  308.         }
  309.  
  310.         /**
  311.          * Getter for packed method
  312.          *
  313.          * @return packed method
  314.          */
  315.  
  316.         private Method getMethod() {
  317.             return method;
  318.         }
  319.  
  320.         /**
  321.          * {@inheritDoc}
  322.          */
  323.  
  324.         @Override
  325.         public boolean equals(Object obj) {
  326.             if (obj == null) {
  327.                 return false;
  328.             }
  329.             if (obj instanceof ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter) {
  330.                 ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter other = (ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter) obj;
  331.                 return Arrays.equals(method.getParameterTypes(), other.method.getParameterTypes())
  332.                         && method.getName().equals(other.method.getName())
  333.                         && method.getReturnType().equals(other.method.getReturnType());
  334.             }
  335.             return false;
  336.         }
  337.  
  338.         /**
  339.          * {@inheritDoc}
  340.          */
  341.  
  342.         @Override
  343.         public int hashCode() {
  344.             return 31 * 31 * Arrays.hashCode(method.getParameterTypes())
  345.                     + 31 * method.getName().hashCode()
  346.                     + method.getReturnType().hashCode();
  347.         }
  348.  
  349.     }
  350.  
  351.  
  352.     /**
  353.      * {@inheritDoc}
  354.      */
  355.  
  356.     @Override
  357.     public void implement(Class<?> token, Path root) throws ImplerException {
  358.         if (token == null || root == null) {
  359.             throw new ImplerException("Not-null arguments expected");
  360.         }
  361.         if (token.equals(Enum.class) || token.isPrimitive() || token.isArray() || token.isEnum() || Modifier.isFinal(token.getModifiers()) || Modifier.isPrivate(token.getModifiers())) {
  362.             throw new ImplerException("bad token");
  363.         }
  364.         Path filepath = getFilePath(token, root, ".java");
  365.         createDirectories(filepath);
  366.         try (Writer writer = Files.newBufferedWriter(filepath)) {
  367.             addPackage(writer, token);
  368.             addClassName(writer, token);
  369.             if (!token.isInterface())
  370.                 addConstructors(writer, token);
  371.             addMethods(writer, token);
  372.             writer.write(toUnicode("}"));
  373.         } catch (IOException e) {
  374.             throw new ImplerException(WRITING_FILE_ERROR + e.getMessage());
  375.         }
  376.     }
  377.  
  378.  
  379.     /**
  380.      * Adds constructors of token into file associated with writer
  381.      *
  382.      * @param writer writer associated with implemented class file
  383.      * @param token  implemented class token
  384.      * @throws ImplerException if given token has no non-private constructors
  385.      *                         or any error occurs during writing into file
  386.      */
  387.  
  388.  
  389.     private void addConstructors(Writer writer, Class<?> token) throws ImplerException {
  390.         Constructor<?>[] constructors = token.getDeclaredConstructors();
  391.         List<Constructor<?>> constructors_ = Arrays.stream(constructors)
  392.                 .filter(x -> !Modifier.isPrivate(x.getModifiers())).collect(Collectors.toList());
  393.         if (constructors_.isEmpty()) {
  394.             throw new ImplerException("No constructors");
  395.         }
  396.         for (Constructor<?> constructor : constructors_) {
  397.             try {
  398.                 writer.write(toUnicode(getExecutable(constructor)));
  399.             } catch (IOException e) {
  400.                 throw new ImplerException(WRITING_FILE_ERROR + e.getMessage());
  401.             }
  402.         }
  403.     }
  404.  
  405.     /**
  406.      * Add methods by applying func on given token, then
  407.      * Packs them by {@link ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter} and inserts into the given {@link Set}
  408.      *
  409.      * @param methods set for insertions
  410.      * @param token   class token
  411.      * @param func    Function receiving token and returning array of methods
  412.      */
  413.  
  414.  
  415.     private void addMethodsToSet(Set<ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter> methods, Class<?> token,
  416.                                  Function<Class<?>, Method[]> func) {
  417.         Arrays.stream(func.apply(token))
  418.                 .filter(x -> Modifier.isAbstract(x.getModifiers()))
  419.                 .map(ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter::new)
  420.                 .collect(Collectors.toCollection(() -> methods));
  421.     }
  422.  
  423.  
  424.     /**
  425.      * Extracts all {@link Modifier#ABSTRACT} methods of given token
  426.      *
  427.      * @param token class token
  428.      * @return set of abstract methods
  429.      */
  430.  
  431.     private Set<Method> getAllAbstractMethods(Class<?> token) {
  432.         Set<ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter> methods = new HashSet<>();
  433.         addMethodsToSet(methods, token, Class::getMethods);
  434.         while (token != null) {
  435.             addMethodsToSet(methods, token, Class::getDeclaredMethods);
  436.             token = token.getSuperclass();
  437.         }
  438.         return methods.stream().map(ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter::getMethod).collect(Collectors.toCollection(HashSet::new));
  439.     }
  440.  
  441.     /**
  442.      * Writes all methods of class implemented token's class into file associated with writer
  443.      *
  444.      * @param wr    writer associated with implementing class file
  445.      * @param token implementing class token
  446.      * @throws ImplerException if any errors occurs during writing
  447.      */
  448.  
  449.     private void addMethods(Writer wr, Class<?> token) throws ImplerException {
  450.         Set<Method> methods = getAllAbstractMethods(token);
  451.         for (Method m : methods) {
  452.             try {
  453.                 wr.write(toUnicode(getExecutable(m)));
  454.             } catch (IOException e) {
  455.                 throw new ImplerException(WRITING_FILE_ERROR + e.getMessage());
  456.             }
  457.         }
  458.     }
  459.  
  460.  
  461.     /**
  462.      * Multiplies {@link ru.ifmo.rain.brichev.implementor.JarImplementor#TAB}
  463.      *
  464.      * @param count number of repetition
  465.      * @return {@link ru.ifmo.rain.brichev.implementor.JarImplementor#TAB} repeated count times
  466.      */
  467.  
  468.  
  469.     private String getTabs(int count) {
  470.         return TAB.repeat(count);
  471.     }
  472.  
  473.     /**
  474.      * Converts the given array of tokens into string.
  475.      * Every token is separated by ", "
  476.      *
  477.      * @param exceptions given array of tokens
  478.      * @return string representation of given token's array
  479.      */
  480.  
  481.  
  482.     private String getExceptions(Class<?>[] exceptions) {
  483.         StringBuilder stringBuilder = new StringBuilder();
  484.         for (Class<?> exception : exceptions) {
  485.             stringBuilder.append(exception.getCanonicalName()).append(", ");
  486.         }
  487.         return stringBuilder.substring(0, stringBuilder.length() - 2);
  488.     }
  489.  
  490.     /**
  491.      * Converts the given executable into default string representation.
  492.      * It includes return type for functions or methods, name, checked exceptions
  493.      * throwing by it, its arguments and body
  494.      *
  495.      * @param exec given executable
  496.      * @return default string representation of given executable
  497.      */
  498.  
  499.  
  500.     private String getExecutable(Executable exec) {
  501.         return getTabs(1) + Modifier.toString(exec.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED))
  502.                 + " " + getReturnTypeAndName(exec)
  503.                 + "(" + getArguments(exec) + ")"
  504.                 + (exec.getExceptionTypes().length == 0
  505.                 ? ""
  506.                 : " throws " + getExceptions(exec.getExceptionTypes()) + " ") + "{" + END_LINE
  507.                 + getBody(exec) + END_LINE
  508.                 + getTabs(1) + "}" + END_LINE + END_LINE;
  509.     }
  510.  
  511.  
  512.     /**
  513.      * Converts the given executable into string containing its default body.
  514.      * The body will contains only return statement with default value
  515.      * concerted with return type of given executable for functions and methods.
  516.      * For constructors it also will contains the call of super method with correct arguments
  517.      * related with given executable arguments
  518.      *
  519.      * @param exec given executable
  520.      * @return default body of given executable
  521.      */
  522.  
  523.     private String getBody(Executable exec) {
  524.         return (exec instanceof Constructor ?
  525.                 getTabs(2) + "super(" + getArguments(exec, false) + ");" + END_LINE : "") +
  526.                 getTabs(2) + "return " + getReturn(exec) + ";";
  527.     }
  528.  
  529.  
  530.     /**
  531.      * Converts the given executable into string containing all its arguments accompanied by their
  532.      * types and separated by ", ".
  533.      * Equivalently to call {@link ru.ifmo.rain.brichev.implementor.JarImplementor#getArguments(Executable, boolean)} with true flag
  534.      *
  535.      * @param exec given executable
  536.      * @return arguments of executable separated by ", "
  537.      */
  538.  
  539.     private String getArguments(Executable exec) {
  540.         return getArguments(exec, true);
  541.     }
  542.  
  543.  
  544.     /**
  545.      * Converts the given executable into string containing all its arguments separated by ", ".
  546.      * If flag is true they will be accompanied by their types
  547.      *
  548.      * @param exec     given executable
  549.      * @param withType identifier of should type be written before the variables names
  550.      * @return arguments of executable separated by ", "
  551.      */
  552.  
  553.     private String getArguments(Executable exec, boolean withType) {
  554.         StringBuilder sb = new StringBuilder();
  555.         Parameter[] parameters = exec.getParameters();
  556.         for (Parameter parameter : parameters) {
  557.             sb.
  558.                     append(withType ? parameter.getType().getCanonicalName() : "")
  559.                     .append(" ").append(parameter.getName()).append(", ");
  560.         }
  561.         return sb.length() > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();
  562.     }
  563.  
  564.     /**
  565.      * Converts the given executable into string containing return type and name
  566.      *
  567.      * @param exec given executable
  568.      * @return string containing return type and name of the given executable
  569.      */
  570.  
  571.     private String getReturnTypeAndName(Executable exec) {
  572.         if (exec instanceof Method) {
  573.             Method m = (Method) exec;
  574.             return m.getReturnType().getCanonicalName() + " " + m.getName();
  575.         } else {
  576.             return exec.getDeclaringClass().getSimpleName() + "Impl";
  577.         }
  578.     }
  579.  
  580.     /**
  581.      * Converts the given executable into string containing return with default value
  582.      *
  583.      * @param exec given executable
  584.      * @return "return" + default value for given executable("" for void functions and constructors) + ";"
  585.      */
  586.  
  587.     private String getReturn(Executable exec) {
  588.         if (exec instanceof Method) {
  589.             Method m = (Method) exec;
  590.             if (m.getReturnType() == void.class) {
  591.                 return "";
  592.             } else if (m.getReturnType().isPrimitive()) {
  593.                 return m.getReturnType() == boolean.class ? "false" : "0";
  594.             }
  595.             return "null";
  596.         }
  597.         return "";
  598.     }
  599.  
  600.     /**
  601.      * Writes definition of class implementing token's class into file associated with writer
  602.      *
  603.      * @param wr    writer associated with implementing class file
  604.      * @param token implementing class token
  605.      * @throws ImplerException if any errors occurs during writing
  606.      */
  607.  
  608.     private void addPackage(Writer wr, Class<?> token) throws ImplerException {
  609.         try {
  610.             if (token.getPackageName() != null) {
  611.                 wr.write(toUnicode("package " + token.getPackageName() + ";" + END_LINE + END_LINE));
  612.             } else {
  613.                 wr.write(toUnicode(" " + END_LINE + END_LINE));
  614.             }
  615.         } catch (IOException e) {
  616.             throw new ImplerException(WRITING_FILE_ERROR + e.getMessage());
  617.         }
  618.     }
  619.  
  620.  
  621.     /**
  622.      * @param wr    writer
  623.      * @param token given token
  624.      * @throws ImplerException if an error occurs
  625.      */
  626.  
  627.     private void addClassName(Writer wr, Class<?> token) throws ImplerException {
  628.         try {
  629.             wr.write(toUnicode("public class " + token.getSimpleName() + "Impl "
  630.                     + (token.isInterface() ? "implements " : "extends ")
  631.                     + token.getCanonicalName()
  632.                     + " {" + END_LINE));
  633.         } catch (IOException e) {
  634.             throw new ImplerException(WRITING_FILE_ERROR + e.getMessage());
  635.         }
  636.     }
  637. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement