Advertisement
Guest User

Untitled

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