Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package ru.ifmo.rain.brichev.implementor;
- import info.kgeorgiy.java.advanced.implementor.JarImpler;
- import info.kgeorgiy.java.advanced.implementor.ImplerException;
- import javax.tools.JavaCompiler;
- import javax.tools.ToolProvider;
- import java.io.*;
- import java.lang.reflect.*;
- import java.net.URISyntaxException;
- import java.nio.file.Files;
- import java.nio.file.InvalidPathException;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.security.CodeSource;
- import java.util.*;
- import java.util.function.Function;
- import java.util.jar.Attributes;
- import java.util.jar.JarOutputStream;
- import java.util.jar.Manifest;
- import java.util.stream.Collectors;
- import java.util.zip.ZipEntry;
- /**
- * Implementation class for {@link JarImpler} interface.
- */
- public class JarImplementor implements JarImpler {
- /**
- * Line separator specified to current OS
- */
- private static final Character PATH_SEPARATOR = File.separatorChar;
- /**
- * End line separator specified to current OS
- */
- private static final String END_LINE = System.lineSeparator();
- /**
- * Template of exception message, that is thrown during writing
- */
- private static final String WRITING_FILE_ERROR = "Writing file error: ";
- /**
- * Whitespace separator.
- */
- private static final String TAB = " ";
- /**
- * Default constructor.
- */
- public JarImplementor() {
- }
- /**
- * Main method.
- * First argument should be "-class" or "-jar".
- * Second argument must be classname and in case of "-jar" option should be name of jar file to generate
- *
- * @param args - arguments
- */
- public static void main(String[] args) {
- if (args == null || (args.length != 2 && args.length != 3)) {
- System.out.println("Wrong arguments format");
- return;
- }
- for (String arg : args) {
- if (arg == null) {
- System.out.println("Null argument appeared");
- return;
- }
- }
- try {
- if (args.length == 2) {
- new ru.ifmo.rain.brichev.implementor.JarImplementor().implement(Class.forName(args[0]), Paths.get(args[1]));
- } else if ("-jar".equals(args[0])) {
- new ru.ifmo.rain.brichev.implementor.JarImplementor().implementJar(Class.forName(args[1]), Paths.get(args[2]));
- } else {
- System.out.println("Wrong arguments format");
- }
- } catch (ClassNotFoundException e) {
- System.out.println("Class not found");
- } catch (InvalidPathException e) {
- System.out.println("Invalid path");
- } catch (ImplerException e) {
- System.err.println(e.getMessage());
- }
- }
- /**
- * Creates an arguments for compiler
- *
- * @param token class
- * @param tmpDir temporary directory
- * @param cp path of class
- * @return compile args
- */
- private String[] getArgs(Class<?> token, Path tmpDir, String cp) {
- String[] args = new String[]{
- "-cp",
- System.getProperty("java.class.path") + File.pathSeparator
- + tmpDir + File.pathSeparator +
- cp,
- tmpDir.resolve(getFilePath(token, tmpDir, ".java")).toString()};
- return args;
- }
- /**
- * Method that clean a temporary directory
- *
- * @param tmpDir temporary directory
- * @throws ImplerException if an error occurs
- */
- private void cleanDirectory(Path tmpDir) throws ImplerException {
- try {
- Files.walk(tmpDir)
- .map(Path::toFile)
- .sorted(Comparator.reverseOrder())
- .forEach(File::delete);
- } catch (IOException e) {
- throw new ImplerException("Failed deleting temporary files in " + tmpDir.toString());
- }
- }
- /**
- * Convert to unicode string
- *
- * @param in input string
- * @return converted string
- */
- private String toUnicode(String in) {
- StringBuilder b = new StringBuilder();
- for (char c : in.toCharArray()) {
- if (c >= 128)
- b.append("\\u").append(String.format("%04X", (int) c));
- else
- b.append(c);
- }
- return b.toString();
- }
- /**
- * @param token given token
- * @return path to implementation of class
- */
- private static String getPathToImplementation(Class<?> token) {
- return Paths.get("").resolve(token.getPackageName().replace('.', '/')).resolve(token.getSimpleName()).toString().replace('\\', '/');
- }
- /**
- * @param token given token
- * @return super class path
- * @throws ImplerException if an error occurs
- */
- private static Path getSuperPath(Class<?> token) throws ImplerException {
- try {
- CodeSource codeSource = token.getProtectionDomain().getCodeSource();
- if (codeSource == null) {
- return Path.of(".");
- } else {
- return Path.of(codeSource.getLocation().toURI());
- }
- } catch (InvalidPathException | URISyntaxException e) {
- throw new ImplerException("Classpath generation failed", e);
- }
- }
- /**
- * @param token given token
- * @param sourceDir source directory
- * @throws ImplerException if an error occurs
- */
- private static void compile(Class<?> token, Path sourceDir) throws ImplerException {
- Path toSuper = getSuperPath(token);
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- if (compiler == null) {
- throw new ImplerException("Compiler is not installed");
- }
- String[] compilationArgs = {"-encoding", "UTF-8",
- "-cp",
- sourceDir.toString() + File.pathSeparator + toSuper.toString(),
- Path.of(sourceDir.toString(), getPathToImplementation(token) + "Impl.java").toString()
- };
- int compilationResultCode = compiler.run(null, null, null, compilationArgs);
- if (compilationResultCode != 0) {
- throw new ImplerException("Compilation failed (result code is not 0): " + compilationResultCode);
- }
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public void implementJar(Class<?> token, Path jarFile) throws ImplerException {
- if (token == null || jarFile == null) {
- throw new ImplerException("Invalid argument: null");
- }
- createDirectories(jarFile);
- Path tmpDir;
- try {
- tmpDir = Files.createTempDirectory(jarFile.toAbsolutePath().getParent(), "tmp");
- } catch (IOException e) {
- throw new ImplerException("Error during creating temporary directory", e);
- }
- try {
- implement(token, tmpDir);
- compile(token, tmpDir);
- Manifest manifest = new Manifest();
- Attributes attributes = manifest.getMainAttributes();
- attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
- attributes.put(Attributes.Name.IMPLEMENTATION_VENDOR, "Anton Brichev");
- try (JarOutputStream stream = new JarOutputStream(Files.newOutputStream(jarFile), manifest)) {
- String implementationPath = getPathToImplementation(token) + "Impl.class";
- stream.putNextEntry(new ZipEntry(implementationPath));
- Files.copy(Path.of(tmpDir.toString(), implementationPath), stream);
- } catch (IOException e) {
- throw new ImplerException("Writing a jar file error ", e);
- }
- } finally {
- cleanDirectory(tmpDir);
- }
- }
- /**
- * Converts the given token into the path of source file
- * potentially implemented and resolved against the given path
- * and add the given file extension
- *
- * @param token given token
- * @param root path to be resolve against
- * @param suffix file extension
- * @return the resulting path
- */
- private Path getFilePath(Class<?> token, Path root, String suffix) {
- return root.resolve(token.getPackageName().replace('.', PATH_SEPARATOR))
- .resolve(token.getSimpleName() + "Impl" + suffix);
- }
- /**
- * Creates directories using given path
- *
- * @param path given path
- * @throws ImplerException if an error occurs
- */
- private void createDirectories(Path path) throws ImplerException {
- if (path.getParent() != null) {
- try {
- Files.createDirectories(path.getParent());
- } catch (IOException e) {
- throw new ImplerException("Can't create directories in: " + path);
- }
- }
- }
- /**
- * Class that pack {@link Method} and provides {@link ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter#equals(Object)} and
- * {@link ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter#hashCode()} depending on packed method
- */
- private static class MethodSetter {
- /**
- * packed method
- */
- final private Method method;
- /**
- * Constructor receiving method for packing
- *
- * @param method method to be packed
- */
- private MethodSetter(Method method) {
- this.method = method;
- }
- /**
- * Getter for packed method
- *
- * @return packed method
- */
- private Method getMethod() {
- return method;
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean equals(Object obj) {
- if (obj == null) {
- return false;
- }
- if (obj instanceof ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter) {
- ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter other = (ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter) obj;
- return Arrays.equals(method.getParameterTypes(), other.method.getParameterTypes())
- && method.getName().equals(other.method.getName())
- && method.getReturnType().equals(other.method.getReturnType());
- }
- return false;
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public int hashCode() {
- return 31 * 31 * Arrays.hashCode(method.getParameterTypes())
- + 31 * method.getName().hashCode()
- + method.getReturnType().hashCode();
- }
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public void implement(Class<?> token, Path root) throws ImplerException {
- if (token == null || root == null) {
- throw new ImplerException("Not-null arguments expected");
- }
- if (token.equals(Enum.class) || token.isPrimitive() || token.isArray() || token.isEnum() || Modifier.isFinal(token.getModifiers()) || Modifier.isPrivate(token.getModifiers())) {
- throw new ImplerException("bad token");
- }
- Path filepath = getFilePath(token, root, ".java");
- createDirectories(filepath);
- try (Writer writer = Files.newBufferedWriter(filepath)) {
- addPackage(writer, token);
- addClassName(writer, token);
- if (!token.isInterface())
- addConstructors(writer, token);
- addMethods(writer, token);
- writer.write(toUnicode("}"));
- } catch (IOException e) {
- throw new ImplerException(WRITING_FILE_ERROR + e.getMessage());
- }
- }
- /**
- * Adds constructors of token into file associated with writer
- *
- * @param writer writer associated with implemented class file
- * @param token implemented class token
- * @throws ImplerException if given token has no non-private constructors
- * or any error occurs during writing into file
- */
- private void addConstructors(Writer writer, Class<?> token) throws ImplerException {
- Constructor<?>[] constructors = token.getDeclaredConstructors();
- List<Constructor<?>> constructors_ = Arrays.stream(constructors)
- .filter(x -> !Modifier.isPrivate(x.getModifiers())).collect(Collectors.toList());
- if (constructors_.isEmpty()) {
- throw new ImplerException("No constructors");
- }
- for (Constructor<?> constructor : constructors_) {
- try {
- writer.write(toUnicode(getExecutable(constructor)));
- } catch (IOException e) {
- throw new ImplerException(WRITING_FILE_ERROR + e.getMessage());
- }
- }
- }
- /**
- * Add methods by applying func on given token, then
- * Packs them by {@link ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter} and inserts into the given {@link Set}
- *
- * @param methods set for insertions
- * @param token class token
- * @param func Function receiving token and returning array of methods
- */
- private void addMethodsToSet(Set<ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter> methods, Class<?> token,
- Function<Class<?>, Method[]> func) {
- Arrays.stream(func.apply(token))
- .filter(x -> Modifier.isAbstract(x.getModifiers()))
- .map(ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter::new)
- .collect(Collectors.toCollection(() -> methods));
- }
- /**
- * Extracts all {@link Modifier#ABSTRACT} methods of given token
- *
- * @param token class token
- * @return set of abstract methods
- */
- private Set<Method> getAllAbstractMethods(Class<?> token) {
- Set<ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter> methods = new HashSet<>();
- addMethodsToSet(methods, token, Class::getMethods);
- while (token != null) {
- addMethodsToSet(methods, token, Class::getDeclaredMethods);
- token = token.getSuperclass();
- }
- return methods.stream().map(ru.ifmo.rain.brichev.implementor.JarImplementor.MethodSetter::getMethod).collect(Collectors.toCollection(HashSet::new));
- }
- /**
- * Writes all methods of class implemented token's class into file associated with writer
- *
- * @param wr writer associated with implementing class file
- * @param token implementing class token
- * @throws ImplerException if any errors occurs during writing
- */
- private void addMethods(Writer wr, Class<?> token) throws ImplerException {
- Set<Method> methods = getAllAbstractMethods(token);
- for (Method m : methods) {
- try {
- wr.write(toUnicode(getExecutable(m)));
- } catch (IOException e) {
- throw new ImplerException(WRITING_FILE_ERROR + e.getMessage());
- }
- }
- }
- /**
- * Multiplies {@link ru.ifmo.rain.brichev.implementor.JarImplementor#TAB}
- *
- * @param count number of repetition
- * @return {@link ru.ifmo.rain.brichev.implementor.JarImplementor#TAB} repeated count times
- */
- private String getTabs(int count) {
- return TAB.repeat(count);
- }
- /**
- * Converts the given array of tokens into string.
- * Every token is separated by ", "
- *
- * @param exceptions given array of tokens
- * @return string representation of given token's array
- */
- private String getExceptions(Class<?>[] exceptions) {
- StringBuilder stringBuilder = new StringBuilder();
- for (Class<?> exception : exceptions) {
- stringBuilder.append(exception.getCanonicalName()).append(", ");
- }
- return stringBuilder.substring(0, stringBuilder.length() - 2);
- }
- /**
- * Converts the given executable into default string representation.
- * It includes return type for functions or methods, name, checked exceptions
- * throwing by it, its arguments and body
- *
- * @param exec given executable
- * @return default string representation of given executable
- */
- private String getExecutable(Executable exec) {
- return getTabs(1) + Modifier.toString(exec.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED))
- + " " + getReturnTypeAndName(exec)
- + "(" + getArguments(exec) + ")"
- + (exec.getExceptionTypes().length == 0
- ? ""
- : " throws " + getExceptions(exec.getExceptionTypes()) + " ") + "{" + END_LINE
- + getBody(exec) + END_LINE
- + getTabs(1) + "}" + END_LINE + END_LINE;
- }
- /**
- * Converts the given executable into string containing its default body.
- * The body will contains only return statement with default value
- * concerted with return type of given executable for functions and methods.
- * For constructors it also will contains the call of super method with correct arguments
- * related with given executable arguments
- *
- * @param exec given executable
- * @return default body of given executable
- */
- private String getBody(Executable exec) {
- return (exec instanceof Constructor ?
- getTabs(2) + "super(" + getArguments(exec, false) + ");" + END_LINE : "") +
- getTabs(2) + "return " + getReturn(exec) + ";";
- }
- /**
- * Converts the given executable into string containing all its arguments accompanied by their
- * types and separated by ", ".
- * Equivalently to call {@link ru.ifmo.rain.brichev.implementor.JarImplementor#getArguments(Executable, boolean)} with true flag
- *
- * @param exec given executable
- * @return arguments of executable separated by ", "
- */
- private String getArguments(Executable exec) {
- return getArguments(exec, true);
- }
- /**
- * Converts the given executable into string containing all its arguments separated by ", ".
- * If flag is true they will be accompanied by their types
- *
- * @param exec given executable
- * @param withType identifier of should type be written before the variables names
- * @return arguments of executable separated by ", "
- */
- private String getArguments(Executable exec, boolean withType) {
- StringBuilder sb = new StringBuilder();
- Parameter[] parameters = exec.getParameters();
- for (Parameter parameter : parameters) {
- sb.
- append(withType ? parameter.getType().getCanonicalName() : "")
- .append(" ").append(parameter.getName()).append(", ");
- }
- return sb.length() > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();
- }
- /**
- * Converts the given executable into string containing return type and name
- *
- * @param exec given executable
- * @return string containing return type and name of the given executable
- */
- private String getReturnTypeAndName(Executable exec) {
- if (exec instanceof Method) {
- Method m = (Method) exec;
- return m.getReturnType().getCanonicalName() + " " + m.getName();
- } else {
- return exec.getDeclaringClass().getSimpleName() + "Impl";
- }
- }
- /**
- * Converts the given executable into string containing return with default value
- *
- * @param exec given executable
- * @return "return" + default value for given executable("" for void functions and constructors) + ";"
- */
- private String getReturn(Executable exec) {
- if (exec instanceof Method) {
- Method m = (Method) exec;
- if (m.getReturnType() == void.class) {
- return "";
- } else if (m.getReturnType().isPrimitive()) {
- return m.getReturnType() == boolean.class ? "false" : "0";
- }
- return "null";
- }
- return "";
- }
- /**
- * Writes definition of class implementing token's class into file associated with writer
- *
- * @param wr writer associated with implementing class file
- * @param token implementing class token
- * @throws ImplerException if any errors occurs during writing
- */
- private void addPackage(Writer wr, Class<?> token) throws ImplerException {
- try {
- if (token.getPackageName() != null) {
- wr.write(toUnicode("package " + token.getPackageName() + ";" + END_LINE + END_LINE));
- } else {
- wr.write(toUnicode(" " + END_LINE + END_LINE));
- }
- } catch (IOException e) {
- throw new ImplerException(WRITING_FILE_ERROR + e.getMessage());
- }
- }
- /**
- * @param wr writer
- * @param token given token
- * @throws ImplerException if an error occurs
- */
- private void addClassName(Writer wr, Class<?> token) throws ImplerException {
- try {
- wr.write(toUnicode("public class " + token.getSimpleName() + "Impl "
- + (token.isInterface() ? "implements " : "extends ")
- + token.getCanonicalName()
- + " {" + END_LINE));
- } catch (IOException e) {
- throw new ImplerException(WRITING_FILE_ERROR + e.getMessage());
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement