From a963b2cb8b90c69b5b37803f87ba69c99ce1c753 Mon Sep 17 00:00:00 2001 From: David Sargent Date: Sun, 29 Jul 2018 21:17:18 -0400 Subject: [PATCH] Add AppVeyor, begin refactor to use STs, use an actual logging framework, change the default private cotr policy --- appveyor.yml | 14 + build.gradle | 5 +- .../java/davidsar/gent/stubjars/JarFile.java | 89 +++ .../gent}/stubjars/Main.java | 53 +- .../gent}/stubjars/Preconditions.java | 2 +- .../gent}/stubjars/StubJars.java | 44 +- .../gent}/stubjars/Utils.java | 2 +- .../components/CompileableExpression.java} | 6 +- .../gent/stubjars/components/Expression.java | 339 ++++++++++++ .../gent/stubjars/components/JarClass.java | 520 ++++++++++++++++++ .../stubjars/components/JarConstructor.java | 91 ++- .../gent}/stubjars/components/JarField.java | 41 +- .../gent/stubjars/components/JarMethod.java | 196 +++++++ .../stubjars/components/JarModifiers.java} | 14 +- .../gent}/stubjars/components/JarType.java | 47 +- .../stubjars/components/SecurityModifier.java | 10 +- .../gent/stubjars/components/Value.java | 80 +++ .../stubjars/components/writer/Constants.java | 13 +- .../components/writer/JavaClassWriter.java | 71 +++ .../stubjars/components/writer/Writer.java | 7 +- .../components/writer/WriterThread.java | 9 +- .../davidsar/gent/stubjars/utils/Streams.java | 49 ++ .../me/davidsargent/stubjars/JarFile.java | 90 --- .../stubjars/components/JarClass.java | 236 -------- .../stubjars/components/JarMethod.java | 127 ----- .../components/writer/JavaClassWriter.java | 387 ------------- src/main/resources/logback.xml | 15 + 27 files changed, 1544 insertions(+), 1013 deletions(-) create mode 100644 appveyor.yml create mode 100644 src/main/java/davidsar/gent/stubjars/JarFile.java rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/Main.java (53%) rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/Preconditions.java (96%) rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/StubJars.java (83%) rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/Utils.java (98%) rename src/main/java/{me/davidsargent/stubjars/components/CompileableString.java => davidsar/gent/stubjars/components/CompileableExpression.java} (83%) create mode 100644 src/main/java/davidsar/gent/stubjars/components/Expression.java create mode 100644 src/main/java/davidsar/gent/stubjars/components/JarClass.java rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/components/JarConstructor.java (68%) rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/components/JarField.java (50%) create mode 100644 src/main/java/davidsar/gent/stubjars/components/JarMethod.java rename src/main/java/{me/davidsargent/stubjars/components/JarModifers.java => davidsar/gent/stubjars/components/JarModifiers.java} (85%) rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/components/JarType.java (80%) rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/components/SecurityModifier.java (67%) create mode 100644 src/main/java/davidsar/gent/stubjars/components/Value.java rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/components/writer/Constants.java (58%) create mode 100644 src/main/java/davidsar/gent/stubjars/components/writer/JavaClassWriter.java rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/components/writer/Writer.java (92%) rename src/main/java/{me/davidsargent => davidsar/gent}/stubjars/components/writer/WriterThread.java (90%) create mode 100644 src/main/java/davidsar/gent/stubjars/utils/Streams.java delete mode 100644 src/main/java/me/davidsargent/stubjars/JarFile.java delete mode 100644 src/main/java/me/davidsargent/stubjars/components/JarClass.java delete mode 100644 src/main/java/me/davidsargent/stubjars/components/JarMethod.java delete mode 100644 src/main/java/me/davidsargent/stubjars/components/writer/JavaClassWriter.java create mode 100644 src/main/resources/logback.xml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..62e9d6c --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,14 @@ +version: 0.2.{build} + +branches: + only: + - master + +build_script: +- sh: ./gradlew assemble + +test_script: +- sh: ./gradlew test + +before_deploy: +- sh: ./gradlew distZip \ No newline at end of file diff --git a/build.gradle b/build.gradle index ce3d354..bc3243c 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,9 @@ apply plugin: 'application' sourceCompatibility = 1.8 -mainClassName = "me.davidsargent.stubjars.Main" +mainClassName = "davidsar.gent.stubjars.Main" repositories { - mavenCentral() + jcenter() } jar { @@ -24,5 +24,6 @@ tasks.withType(AbstractArchiveTask) { dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' + compile 'ch.qos.logback:logback-classic:1.3.+' compile 'org.jetbrains:annotations:15.0' } diff --git a/src/main/java/davidsar/gent/stubjars/JarFile.java b/src/main/java/davidsar/gent/stubjars/JarFile.java new file mode 100644 index 0000000..e064d67 --- /dev/null +++ b/src/main/java/davidsar/gent/stubjars/JarFile.java @@ -0,0 +1,89 @@ +/* + * Copyright 2018 David Sargent + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package davidsar.gent.stubjars; + +import davidsar.gent.stubjars.components.JarClass; +import davidsar.gent.stubjars.utils.Streams; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class JarFile { + private static Map jarFiles = new HashMap<>(); + private final File jar; + + private JarFile(File jar) { + this.jar = jar; + } + + static JarFile forFile(@NotNull File jar) { + if (jarFiles.containsKey(jar)) return jarFiles.get(jar); + + JarFile jarFile = new JarFile(jar); + jarFiles.put(jar, jarFile); + return jarFile; + } + + @NotNull + static ClassLoader createClassLoaderFromJars(@Nullable ClassLoader parentClassLoader, JarFile... jarFiles) { + URL[] urls = Arrays.stream(jarFiles).map(JarFile::getUrl).toArray(URL[]::new); + ClassLoader classLoader = parentClassLoader == null ? JarFile.class.getClassLoader() : parentClassLoader; + return new URLClassLoader(urls, classLoader); + } + + @NotNull + private URL getUrl() { + try { + return jar.toURI().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(String.format("Could not create classloader for \"%s\"", jar.getAbsolutePath()), e); + } + } + + Set> getClasses(ClassLoader loader) throws IOException { + java.util.jar.JarFile iJar = new java.util.jar.JarFile(jar); + return Streams.makeFor(iJar.entries()) + .filter(jarEntry -> jarEntry.getName().endsWith(".class")) + .map(entry -> { + try { + return new JarClass<>(loader, entry.getName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .flatMap(klazz -> Stream.concat(Stream.of(klazz), findInnerClasses(klazz))) + .collect(Collectors.toSet()); + } + + @NotNull + private Stream> findInnerClasses(@NotNull JarClass jarClass) { + return jarClass.innerClasses().stream() + .flatMap(klazz -> { + Stream> stream = klazz.innerClasses().stream(); + Stream> innerClasses = findInnerClasses(klazz); + return Stream.concat(Stream.concat(Stream.of(klazz), stream), innerClasses); + }); + } +} diff --git a/src/main/java/me/davidsargent/stubjars/Main.java b/src/main/java/davidsar/gent/stubjars/Main.java similarity index 53% rename from src/main/java/me/davidsargent/stubjars/Main.java rename to src/main/java/davidsar/gent/stubjars/Main.java index 0bea06c..bfe07ef 100644 --- a/src/main/java/me/davidsargent/stubjars/Main.java +++ b/src/main/java/davidsar/gent/stubjars/Main.java @@ -11,13 +11,18 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars; +package davidsar.gent.stubjars; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.util.ArrayList; import java.util.List; class Main { + private static final Logger log = LoggerFactory.getLogger(Main.class); + public static void main(String... args) { StubJars.Builder builder = StubJars.builder(); List files = new ArrayList<>(args.length); @@ -27,38 +32,34 @@ public static void main(String... args) { continue; } -// if (argument.contains("*")) { -// String baseFolder = argument.startsWith("*") ? new File(".").getPath() : argument.substring(0, argument.lastIndexOf(File.separatorChar) + 1); -// String regexToCheck = argument.substring(argument.lastIndexOf("/" + 1, argument.indexOf('*'))); -// regexToCheck = argument.substring(0, regexToCheck.indexOf("*") + 1); -// regexToCheck = regexToCheck.replace("*", "\\.*"); -// List matches = new ArrayList<>(); -// for (String possibleResult : Objects.requireNonNull(new File(baseFolder).list())) { -// if (possibleResult.matches(regexToCheck)) { -// matches.add(new File(Path)); -// } -// } -// } - File e = new File(argument); - if (!e.exists()) System.err.println(String.format("The file \"%s\" does not exist!", argument)); - if (e.isDirectory()) System.err.println(String.format("The file \"%s\" is a folder!", argument)); - if (!e.canRead()) System.err.println(String.format("The file \"%s\" cannot be read!", argument)); - files.add(e); + if (!e.exists()) { + log.error("The file \"{}\" does not exist!", argument); + } else if (e.isDirectory()) { + log.error("The file \"{}\" is a folder!", argument); + } else if (!e.canRead()) { + log.error("The file \"{}\" cannot be read!", argument); + } else { + files.add(e); + continue; + } + + log.error("Encountered an error loading the specified JAR files"); + System.exit(1); } + builder.addJars(files.toArray(new File[] {})); - System.out.print("Loading..."); + log.info("Loading the JARs to be stubbed"); StubJars build = builder.build(); - System.out.println("done!"); - System.out.print("Building directory tree at \"" + build.getSourceDestination().getAbsolutePath() + "\"..."); + log.info("JAR load finished"); + log.info("Creating the stub_src directory tree at \"{}\"", build.getSourceDestination().getAbsolutePath()); build.createDirectoryTree(); - System.out.println("done!"); - System.out.print("Writing source files..."); + log.info("stub_src directory tree creation finished"); + log.info("Starting creation of stub_src files"); build.createSourceFiles(); - System.out.println("done!"); - + log.info("Creation of stub_src files finished"); - System.out.println("\nDone! Bye!"); + log.info("StubJars has finished"); } private static void parseArg(StubJars.Builder builder, String arg) { diff --git a/src/main/java/me/davidsargent/stubjars/Preconditions.java b/src/main/java/davidsar/gent/stubjars/Preconditions.java similarity index 96% rename from src/main/java/me/davidsargent/stubjars/Preconditions.java rename to src/main/java/davidsar/gent/stubjars/Preconditions.java index a5d93da..48dc216 100644 --- a/src/main/java/me/davidsargent/stubjars/Preconditions.java +++ b/src/main/java/davidsar/gent/stubjars/Preconditions.java @@ -11,7 +11,7 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars; +package davidsar.gent.stubjars; import org.jetbrains.annotations.Contract; diff --git a/src/main/java/me/davidsargent/stubjars/StubJars.java b/src/main/java/davidsar/gent/stubjars/StubJars.java similarity index 83% rename from src/main/java/me/davidsargent/stubjars/StubJars.java rename to src/main/java/davidsar/gent/stubjars/StubJars.java index 614c617..40fde81 100644 --- a/src/main/java/me/davidsargent/stubjars/StubJars.java +++ b/src/main/java/davidsar/gent/stubjars/StubJars.java @@ -11,13 +11,13 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars; +package davidsar.gent.stubjars; -import me.davidsargent.stubjars.components.JarClass; -import me.davidsargent.stubjars.components.SecurityModifier; -import me.davidsargent.stubjars.components.writer.JavaClassWriter; -import me.davidsargent.stubjars.components.writer.Writer; -import me.davidsargent.stubjars.components.writer.WriterThread; +import davidsar.gent.stubjars.components.JarClass; +import davidsar.gent.stubjars.components.SecurityModifier; +import davidsar.gent.stubjars.components.writer.JavaClassWriter; +import davidsar.gent.stubjars.components.writer.Writer; +import davidsar.gent.stubjars.components.writer.WriterThread; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -43,7 +43,7 @@ public class StubJars { private final File CLASSES_DIR = new File(BUILD_DIR, "classes"); private final File SOURCES_LIST_FILE = new File(SOURCE_DIR, "sources.list"); - private StubJars(@NotNull ConcurrentMap, JarClass> klazzes, @NotNull ClassLoader classLoader) { + private StubJars(@NotNull ConcurrentMap, JarClass> klazzes) { this.klazzes = klazzes; } @@ -53,11 +53,11 @@ private StubJars(@NotNull ConcurrentMap, JarClass> klazzes, @NotNull * @return a new StubJars builder */ @NotNull - public static Builder builder() { + static Builder builder() { return new Builder(); } - public void createDirectoryTree() { + void createDirectoryTree() { if (packages == null) buildPackagesList(); SOURCE_DIR.mkdirs(); createBuildDir(); @@ -75,7 +75,7 @@ private void buildPackagesList() { } } - public void createSourceFiles() { + void createSourceFiles() { WriterThread writerThread = new WriterThread(); writerThread.start(); StringBuilder sourceFiles = new StringBuilder(); @@ -84,7 +84,7 @@ public void createSourceFiles() { continue; // this breaks compilation (currently) // todo: make unneeded - if (e.getKlazz().getName().equals("java.lang.Enum")) + if (e.getKlazz().getName().equals(Enum.class.getName())) continue; File file = new File(SOURCE_DIR, e.getKlazz().getName().replace('.', File.separatorChar) + ".java"); JavaClassWriter writer = new JavaClassWriter(file, e, writerThread); @@ -114,15 +114,14 @@ private void createBuildDir() { CLASSES_DIR.mkdirs(); } - @NotNull - public File getSourceDestination() { + @NotNull File getSourceDestination() { return SOURCE_DIR; } /** * Creates new {@link StubJars} instances */ - public static class Builder { + static class Builder { private final List jars; private final List classpathJars; @@ -136,7 +135,7 @@ private Builder() { * * @param jar a {@link File} representing a JAR file */ - public void addJar(@NotNull File jar) { + void addJar(@NotNull File jar) { jars.add(JarFile.forFile(jar)); } @@ -145,7 +144,7 @@ public void addJar(@NotNull File jar) { * * @param jar a {@link File} representing a JAR file */ - public void addClasspathJar(@NotNull File jar) { + void addClasspathJar(@NotNull File jar) { classpathJars.add(JarFile.forFile(jar)); } @@ -154,7 +153,7 @@ public void addClasspathJar(@NotNull File jar) { * * @param jars the {@link File}s representing a JAR files */ - public void addJars(@NotNull File... jars) { + void addJars(@NotNull File... jars) { for (File jar : jars) { addJar(jar); } @@ -165,16 +164,15 @@ public void addJars(@NotNull File... jars) { * * @return a new {@link StubJars} instance */ - @NotNull - public StubJars build() { - ClassLoader cpClassLoader = JarFile.createClassLoaderFromJars(null, classpathJars.toArray(new JarFile[classpathJars.size()])); - ClassLoader classLoader = JarFile.createClassLoaderFromJars(cpClassLoader, jars.toArray(new JarFile[jars.size()])); + @NotNull StubJars build() { + ClassLoader cpClassLoader = JarFile.createClassLoaderFromJars(null, classpathJars.toArray(new JarFile[0])); + ClassLoader classLoader = JarFile.createClassLoaderFromJars(cpClassLoader, jars.toArray(new JarFile[0])); ConcurrentMap, JarClass> klazzes = new ConcurrentHashMap<>(); for (JarFile jar : jars) { final Set> classes; try { classes = jar.getClasses(classLoader); - } catch (IOException | ClassNotFoundException e) { + } catch (IOException e) { throw new RuntimeException("Cannot load jar!", e); } @@ -184,7 +182,7 @@ public StubJars build() { } JarClass.loadClassToJarClassMap(klazzes); - return new StubJars(klazzes, classLoader); + return new StubJars(klazzes); } } } diff --git a/src/main/java/me/davidsargent/stubjars/Utils.java b/src/main/java/davidsar/gent/stubjars/Utils.java similarity index 98% rename from src/main/java/me/davidsargent/stubjars/Utils.java rename to src/main/java/davidsar/gent/stubjars/Utils.java index d18e510..76fd85a 100644 --- a/src/main/java/me/davidsargent/stubjars/Utils.java +++ b/src/main/java/davidsar/gent/stubjars/Utils.java @@ -11,7 +11,7 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars; +package davidsar.gent.stubjars; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/me/davidsargent/stubjars/components/CompileableString.java b/src/main/java/davidsar/gent/stubjars/components/CompileableExpression.java similarity index 83% rename from src/main/java/me/davidsargent/stubjars/components/CompileableString.java rename to src/main/java/davidsar/gent/stubjars/components/CompileableExpression.java index 9202456..0aa7444 100644 --- a/src/main/java/me/davidsargent/stubjars/components/CompileableString.java +++ b/src/main/java/davidsar/gent/stubjars/components/CompileableExpression.java @@ -11,8 +11,8 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars.components; +package davidsar.gent.stubjars.components; -public interface CompileableString { - String compileToString(); +interface CompileableExpression { + Expression compileToExpression(); } diff --git a/src/main/java/davidsar/gent/stubjars/components/Expression.java b/src/main/java/davidsar/gent/stubjars/components/Expression.java new file mode 100644 index 0000000..79d6b7c --- /dev/null +++ b/src/main/java/davidsar/gent/stubjars/components/Expression.java @@ -0,0 +1,339 @@ +/* + * Copyright 2018 David Sargent + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package davidsar.gent.stubjars.components; + +import davidsar.gent.stubjars.components.writer.Constants; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public abstract class Expression { + StatementExpression statement() { + return statement(this); + } + + private static StatementExpression statement(Expression expression) { + return new StatementExpression(expression); + } + + @NotNull + static BlockStatement block() { + return new BlockStatement(); + } + + @NotNull + static BlockStatement block(StatementExpression... statements) { + return new BlockStatement(statements); + } + + @NotNull + static BlockStatement block(String... statements) { + return new BlockStatement(Arrays.stream(statements).map(Expression::stringAsStatement).toArray(StatementExpression[]::new)); + } + + static Parenthetical parenthetical(String inner) { + return new Parenthetical(new StringExpression(inner)); + } + + static Parenthetical parenthetical(Expression inner) { + return new Parenthetical(inner); + } + + @NotNull + static String cast(String type, String value) { + return cast(of(type), of(value)); + } + + @NotNull + private static String cast(StringExpression type, StringExpression value) { + return parenthetical(type) + Constants.SPACE + value; + } + + @NotNull + static String cast(Class type, Object value) { + return cast(type.getSimpleName(), value.toString()); + } + + @NotNull + static MethodCall methodCall(String methodName) { + return methodCall(methodName, Constants.EMPTY_STRING); + } + + @NotNull + static MethodCall methodCall(String methodName, String params) { + return new MethodCall(new StringExpression(methodName), parenthetical(params)); + } + + Expression spaceAfter() { + return spaceAfter(this); + } + + @NotNull + private static Expression spaceAfter(@NotNull Expression expression) { + if (!expression.hasChildren() && expression.toString().equals(Constants.EMPTY_STRING)) { + return StringExpression.EMPTY; + } + + return of(expression, StringExpression.SPACE); + } + + @NotNull + static Expression spaceAfter(@NotNull String string) { + if (string.isEmpty()) return StringExpression.EMPTY; + + return spaceAfter(of(string)); + } + + @NotNull + static TypeExpression forType(Type type, String typeString) { + return new TypeExpression(type, typeString); + } + + static Expression when(boolean condition, Expression expression) { + if (condition) return expression; + + return StringExpression.EMPTY; + } + + @NotNull + static Expression whenWithSpace(boolean condition, String string) { + if (condition) return spaceAfter(string); + + return StringExpression.EMPTY; + } + + private static StatementExpression stringAsStatement(String statement) { + return new StatementExpression(statement); + } + + private static String stringOf(Expression... expressions) { + return Stream.of(expressions).map(Expression::toString).collect(Collectors.joining()); + } + + static Expression of(Expression... expressions) { + return new GenericExpression(expressions); + } + + private static Expression[] flatten(Collection children) { + return flatten(children.stream()); + } + + private static Expression[] flatten(@NotNull Stream expressionStream) { + return expressionStream.flatMap(expression -> { + if (expression.hasChildren()) { + return Stream.of(flatten(expression.children())); + } + + return Stream.of(expression); + }).toArray(Expression[]::new); + } + + static StringExpression of(String string) { + return new StringExpression(string); + } + + protected abstract boolean hasChildren(); + + protected abstract List children(); + + @Override + public String toString() { + return stringOf(flatten(children())); + } + + public final static class StringExpression extends Expression { + static final Expression LPAREN = new StringExpression(Constants.LEFT_PAREN); + static final Expression RPAREN = new StringExpression(Constants.RIGHT_PAREN); + static final Expression SPACE = new StringExpression(Constants.SPACE); + static final Expression COMMA = new StringExpression(Constants.COMMA); + static final Expression INDENT = new StringExpression(Constants.INDENT); + static final Expression PERIOD = new StringExpression(Constants.PERIOD); + static final Expression AT = new StatementExpression(Constants.AT); + private static final Expression LCURLY = new StringExpression(Constants.LCURLY); + private static final Expression RCURLY = new StringExpression(Constants.RCURLY); + static final StringExpression EMPTY = new StringExpression(Constants.EMPTY_STRING); + static final StringExpression SEMICOLON = new StringExpression(Constants.SEMICOLON); + static final StringExpression NEW_LINE = new StringExpression(Constants.NEW_LINE_CHARACTER); + private final String data; + private static final Expression[] EMPTY_ARRAY = new Expression[0]; + + private StringExpression(String data) { + this.data = data; + } + + @Override + public boolean hasChildren() { + return false; + } + + @Override + public List children() { + return Collections.emptyList(); + } + + @Override + public String toString() { + return data; + } + } + + public static class StatementExpression extends Expression { + private final Expression expression; + private final boolean implString; + private List children; + + private StatementExpression(Expression expression) { + this.expression = expression; + implString = false; + } + + private StatementExpression(String statement) { + expression = new StringExpression(statement); + implString = true; + } + + @Override + public boolean hasChildren() { + return true; + } + + @Override + public List children() { + if (children == null) { + children = Collections.unmodifiableList(buildChildrenList()); + } + + return children; + } + + @NotNull + private List buildChildrenList() { + if (implString) { + return Collections.singletonList(expression); + } + + return Arrays.asList(expression, StringExpression.SEMICOLON, StringExpression.NEW_LINE); + } + } + + public final static class BlockStatement extends Expression { + private final StatementExpression[] statements; + private List children; + + private BlockStatement(StatementExpression... statements) { + this.statements = statements; + } + + @Contract(pure = true) + @Override + public boolean hasChildren() { + return true; + } + + @Override + public List children() { + if (children == null) { + children = Collections.unmodifiableList(buildChildren()); + } + + return children; + } + + @NotNull + private List buildChildren() { + if (statements.length == 0) { + return Arrays.asList(StringExpression.LCURLY, StringExpression.RCURLY); + } + + Expression indentedStatements = of(Arrays.stream(statements) + .map(statement -> of(StringExpression.INDENT, statement)) + .toArray(Expression[]::new) + ); + return Collections.unmodifiableList(Arrays.asList(StringExpression.LCURLY, StringExpression.NEW_LINE, + indentedStatements, + StringExpression.RCURLY, StringExpression.NEW_LINE + )); + + } + } + + public final static class MethodCall extends StatementExpression { + private MethodCall(StringExpression method, Parenthetical expression) { + super(Expression.of(method, expression)); + } + } + + private static class Parenthetical extends Expression { + private final Expression innerExpression; + + private Parenthetical(Expression innerExpression) { + this.innerExpression = innerExpression; + } + + @Override + public boolean hasChildren() { + return true; + } + + @Override + public List children() { + return Arrays.asList(StringExpression.LPAREN, innerExpression, StringExpression.RPAREN); + } + } + + public static class TypeExpression extends Expression { + private final Expression expression; + private final Type type; + + private TypeExpression(Type type, String expression) { + this.type = type; + this.expression = Expression.of(expression); + } + + @Override + public boolean hasChildren() { + return true; + } + + @Override + public List children() { + return Collections.singletonList(expression); + } + } + + private final static class GenericExpression extends Expression { + private final Expression[] children; + + private GenericExpression(Expression[] children) { + this.children = children; + } + + @Override + public boolean hasChildren() { + return true; + } + + @Override + public List children() { + return Arrays.asList(children); + } + } +} diff --git a/src/main/java/davidsar/gent/stubjars/components/JarClass.java b/src/main/java/davidsar/gent/stubjars/components/JarClass.java new file mode 100644 index 0000000..5a9e06a --- /dev/null +++ b/src/main/java/davidsar/gent/stubjars/components/JarClass.java @@ -0,0 +1,520 @@ +/* + * Copyright 2018 David Sargent + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package davidsar.gent.stubjars.components; + +import davidsar.gent.stubjars.Utils; +import davidsar.gent.stubjars.components.writer.Constants; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class JarClass extends JarModifiers implements CompileableExpression { + private static final Logger log = LoggerFactory.getLogger(JarClass.class); + private final static Pattern classEntryPatternToBeStripped = Pattern.compile("\\.class$"); + private static Map, JarClass> classToJarClassMap; + private final Class klazz; + private Set constructors; + private Set methods; + private Set> innerClasses; + + public JarClass(@NotNull ClassLoader classLoader, @NotNull String entryName) throws ClassNotFoundException { + String convertedName = convertEntryNameToClassName(entryName); + //noinspection unchecked + klazz = (Class) Class.forName(convertedName, false, classLoader); + } + + private JarClass(@NotNull Class klazz) { + this.klazz = klazz; + } + + public static void loadClassToJarClassMap(@NotNull Map, @NotNull JarClass> map) { + classToJarClassMap = map; + } + + @NotNull + static JarClass forClass(@NotNull Class klazz) { + if (classToJarClassMap != null && classToJarClassMap.containsKey(klazz)) return classToJarClassMap.get(klazz); + + return new JarClass<>(klazz); + } + + @NotNull + private static String convertEntryNameToClassName(@NotNull String entryName) { + Matcher matcher = classEntryPatternToBeStripped.matcher(entryName); + if (matcher.find()) entryName = matcher.replaceAll(Constants.EMPTY_STRING); + return entryName.replace('/', '.'); + } + + boolean isEnum() { + return klazz.isEnum(); + } + + @NotNull + public Class getKlazz() { + return klazz; + } + + public boolean isInnerClass() { + return klazz.getDeclaringClass() != null || klazz.isLocalClass() || klazz.isAnonymousClass(); + } + + @NotNull + public String name() { + return klazz.getSimpleName(); + } + + static boolean hasSafeName(@NotNull Class klazz) { + JarClass jarClass = JarClass.forClass(klazz); + return !jarClass.klazz.isSynthetic() && !jarClass.klazz.isAnonymousClass(); + } + + @NotNull + static String safeFullNameForClass(@NotNull Class klazz) { + if (!hasSafeName(klazz)) + throw new IllegalArgumentException("Class does not have safe name."); + if (klazz.isArray()) + return safeFullNameForClass(klazz.getComponentType()) + "[]"; + String s = klazz.getName().replaceAll("\\$\\d*", "."); + if (s.endsWith(".")) + throw new IllegalArgumentException("Class does not have safe name."); + return s; + } + + @NotNull + public String packageName() { + return klazz.getPackage().getName(); + } + + @Nullable + public Class extendsClass() { + Class superclass = klazz.getSuperclass(); + return superclass == Object.class ? null : superclass; + } + + @NotNull + private Class[] implementsInterfaces() { + return klazz.getInterfaces(); + } + + @NotNull + private Type[] implementsGenericInterfaces() { + return klazz.getGenericInterfaces(); + } + + boolean isInterface() { + return klazz.isInterface(); + } + + boolean isAnnotation() { + return klazz.isAnnotation(); + } + + @Override + protected int getModifiers() { + return klazz.getModifiers(); + } + + private Set fields() { + return Arrays.stream(klazz.getDeclaredFields()) + .map(field -> new JarField(this, field)) + .filter(field -> field.security() != SecurityModifier.PRIVATE) + .collect(Collectors.toSet()); + } + + @NotNull + public Set> innerClasses() { + if (innerClasses == null) { + innerClasses = Arrays.stream(klazz.getDeclaredClasses()) + .filter(klazz -> !klazz.isLocalClass()) + .filter(klazz -> !klazz.isAnonymousClass()) + .map(JarClass::forClass).collect(Collectors.toSet()); + } + return innerClasses; + } + + @NotNull + private Set methods() { + if (methods == null) { + methods = Arrays.stream(klazz.getDeclaredMethods()) + .map(method1 -> new JarMethod(this, method1)) + .filter(method -> method.security() != SecurityModifier.PRIVATE) + .filter(method -> !method.isSynthetic()) + .filter(JarMethod::shouldIncludeStaticMethod) + .collect(Collectors.toSet()); + } + return methods; + } + + @Override + public boolean equals(Object o) { + return o instanceof JarClass && klazz.equals(((JarClass) o).klazz); + } + + @Override + public int hashCode() { + return klazz.hashCode(); + } + + @NotNull Set allSuperClassesAndInterfaces() { + return allSuperClassesAndInterfaces(klazz).stream().map(JarClass::forClass).collect(Collectors.toSet()); + } + + @NotNull + private static Set> allSuperClassesAndInterfaces(@NotNull Class klazz) { + Set> superClasses = allSuperClasses(klazz, new HashSet<>()); + Set> interfaces = new HashSet<>(); + Collections.addAll(interfaces, klazz.getInterfaces()); + for (Class kInterface : superClasses) { + Collections.addAll(interfaces, kInterface.getInterfaces()); + } + + superClasses.addAll(interfaces); + return superClasses; + } + + @NotNull + private static Set> allSuperClasses(@NotNull Class klazz, @NotNull Set> superClasses) { + Class superClass = klazz.getSuperclass(); + if (superClass == null) + return superClasses; + superClasses.add(superClass); + return allSuperClasses(superClass, superClasses); + } + + boolean hasMethod(@NotNull Method method) { + try { + klazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + @NotNull + private Type extendsGenericClass() { + return klazz.getGenericSuperclass(); + } + + @NotNull Set constructors() { + //noinspection unchecked + if (constructors == null) { + //noinspection unchecked + constructors = Arrays.stream(klazz.getDeclaredConstructors()) + .map(x -> new JarConstructor(this, x)) + .filter(JarConstructor::shouldIncludeCotr) + .collect(Collectors.toSet()); + + constructors.stream() + .filter(JarConstructor::canRewriteConstructorParams).findAny() + .ifPresent(jarConstructor -> constructors = Stream.concat( + constructors.stream() + .filter(cotr -> !(cotr.canRewriteConstructorParams() || cotr.parameters().length == 0)), + Stream.of(jarConstructor) + ).collect(Collectors.toSet())); + } + + return constructors; + } + + @Override + public Expression compileToExpression() { + return compileClass(false, null); + } + + @NotNull + private Expression compileClass(boolean isEnumConstant, String enumName) { + final String methods = compileMethods(isEnumConstant); + final String fields = compileFields(); + final Expression klazzHeader; + final String cotrs; + final String innerClasses; + if (isEnumConstant) { + cotrs = Constants.EMPTY_STRING; + innerClasses = Constants.EMPTY_STRING; + klazzHeader = Expression.spaceAfter(enumName); + } else { + cotrs = compileCotr(); + innerClasses = compileInnerClasses(); + klazzHeader = compileHeader(); + } + + // Enums need to be handled quite a bit differently, but we also need to check if we are working on + // an enum constant to prevent infinite recursion + if (isEnum() && !isEnumConstant) { + Expression enumMembers = Expression.StringExpression.EMPTY.statement(); + //noinspection unchecked + Enum[] invokedExpression = getEnumConstants(); + + if (invokedExpression != null) { + enumMembers = new EnumMembers(Arrays.stream(invokedExpression) + .map(member -> JarClass.forClass(member.getClass()).compileClass(true, member.name())) + .toArray(Expression[]::new)).statement(); + } + + return Expression.of(Expression.of(klazzHeader), Expression.block(enumMembers.toString(), fields, methods, innerClasses)); + } + + return Expression.of(Expression.of(klazzHeader), Expression.block(fields, cotrs, methods, innerClasses)); + } + + @NotNull + private String compileFields() { + return this.fields().stream() + .filter(field -> + !(field.getClazz().isEnum() || field.getClazz().isInnerClass()) && !field.isStatic() && !field.isSynthetic()) + .filter(field -> { + Class superClazz = field.getClazz().extendsClass(); + if (superClazz != null) { + try { + superClazz.getDeclaredField(field.name()); + return false; + } catch (NoSuchFieldException ignored) { + + } + } + + return true; + }) + .map(field -> Constants.INDENT + field.compileToExpression() + Constants.NEW_LINE_CHARACTER) + .collect(Collectors.joining()); + } + + @NotNull + private String compileMethods(boolean isEnumConstant) { + String methods = this.methods().stream() + .map(method -> method.compileToString(isEnumConstant).toString()) + .flatMap(x -> Arrays.stream(x.split(Constants.NEW_LINE_CHARACTER))).collect(Collectors.joining(System.lineSeparator() + Constants.INDENT)); + + if (methods.endsWith(Constants.NEW_LINE_CHARACTER)) { + methods = methods.substring(0, methods.length() - 1); + } + + return methods; + } + + @NotNull + private String compileInnerClasses() { + Set> innerClasses = innerClasses(); + if (innerClasses.size() == 0) return Constants.EMPTY_STRING; + return innerClasses.stream() + .map(x -> (Constants.NEW_LINE_CHARACTER + x.compileToExpression() + Constants.NEW_LINE_CHARACTER).split(Constants.NEW_LINE_CHARACTER)) + .flatMap(Arrays::stream) + .collect(Collectors.joining(System.lineSeparator() + Constants.INDENT)); + } + + @NotNull + private String compileCotr() { + // Interfaces don't have constructors + if (isInterface()) return Constants.EMPTY_STRING; + return constructors().stream() + .map(JarConstructor::compileToExpression) + .flatMap(cotr -> Arrays.stream(cotr.toString().split(Constants.NEW_LINE_CHARACTER))) + .map(cotr -> Constants.INDENT + cotr) + .collect(Collectors.joining(Constants.NEW_LINE_CHARACTER)); + } + + @NotNull + private Expression compileHeader() { + return new ClassHeaderExpression(); + } + + @NotNull + private String compileHeaderImplements() { + StringBuilder implementsS = new StringBuilder(); + if (implementsInterfaces().length > 0 && !(isAnnotation() && implementsInterfaces().length == 1)) { + implementsS = klazz.isInterface() ? new StringBuilder("extends ") : new StringBuilder("implements "); + implementsS.append(Utils.arrayToCommaSeparatedList(implementsGenericInterfaces(), x -> { + if (x.equals(Annotation.class)) return null; + + return JarType.toString(x); + })); + implementsS.append(Constants.SPACE); + } + return implementsS.toString(); + } + + @NotNull + private String compileHeaderExtends() { + final String extendsS; + Class extendsClazz = extendsClass(); + if (extendsClazz != null && !(extendsClazz.equals(Enum.class))) { + extendsS = "extends " + JarType.toString(extendsGenericClass()) + Constants.SPACE; + } else { + extendsS = Constants.EMPTY_STRING; + } + return extendsS; + } + + private String compileTypeParameters() { + final String genericS; + TypeVariable>[] typeParameters = getKlazz().getTypeParameters(); + genericS = JarType.convertTypeParametersToString(typeParameters); + return genericS; + } + + @NotNull + private Expression compileHeaderAnnotation() { + final Expression annotationS; + if (isAnnotation() && getKlazz().isAnnotationPresent(Retention.class)) { + RetentionPolicy retentionPolicy = getKlazz().getAnnotation(Retention.class).value(); + annotationS = Expression.of( + Expression.StringExpression.AT, + Expression.forType(Retention.class, JarClass.safeFullNameForClass(Retention.class)), + Expression.parenthetical(Expression.of( + Expression.of(safeFullNameForClass(RetentionPolicy.class)), + Expression.StringExpression.PERIOD, + Expression.of(retentionPolicy.name()))), + Expression.StringExpression.SPACE + ); + } else { + annotationS = Expression.StringExpression.EMPTY; + } + return annotationS; + } + + @NotNull + private static String typeString(@NotNull JarClass klazz, boolean enumTypeClass) { + final String typeS; + if (klazz.isAnnotation()) { + typeS = "@interface "; + } else if (klazz.isInterface()) { + typeS = "interface "; + } else if (enumTypeClass) { + typeS = "enum "; + } else { + typeS = "class "; + } + return typeS; + } + + private ENUM_CLASS[] getEnumConstants() { + if (!isEnum()) + throw new IllegalArgumentException("Not an enum"); + //noinspection unchecked + return JarClass.getEnumConstantsFor((Class) this.klazz); + } + + @Nullable + static T[] getEnumConstantsFor(@NotNull Class klazz) { + T[] invokedExpression = null; + try { + Method values = klazz.getMethod("values"); + values.setAccessible(true); + //noinspection unchecked + invokedExpression = (T[]) values.invoke(null); + } catch (NoSuchMethodException | IllegalAccessException | ExceptionInInitializerError | NoClassDefFoundError e) { + log.warn("Failed to load enum \"{}\"; reason: access encountered {}", klazz.getName(), e.toString()); + } catch (InvocationTargetException ex) { + log.warn("Failed to load enum \"{}\"; reason: loading encountered {}", klazz.getName(), ex.getTargetException().toString()); + } + + return invokedExpression; + } + + private static class EnumMembers extends ListExpression { + EnumMembers(Expression[] expressions) { + super(expressions, ListExpression.DELIMITER_COMMA_NEW_LINE); + } + } + + private static class ListExpression extends Expression { + static Expression[] DELIMITER_COMMA_NEW_LINE = new Expression[]{StringExpression.COMMA, StringExpression.NEW_LINE}; + public static Expression[] DELIMITER_COMMA_SPACE = new Expression[]{StringExpression.COMMA, StringExpression.SPACE}; + private final Expression[] expressions; + private final Expression[] delimiters; + private List children; + + private ListExpression(Expression[] expressions, Expression[] delimiters) { + this.expressions = expressions; + this.delimiters = delimiters; + } + + @Override + public boolean hasChildren() { + return true; + } + + @Override + public List children() { + if (children == null) { + children = buildChildrenList(); + } + + return children; + } + + @NotNull + private List buildChildrenList() { + List expressionChildren = new ArrayList<>(expressions.length * (delimiters.length + 1) - delimiters.length); + for (int iExpression = 0; iExpression < expressions.length; iExpression++) { + expressionChildren.add(expressions[iExpression]); + if (iExpression < expressions.length - 1) { + expressionChildren.addAll(Arrays.asList(delimiters)); + } + } + + return expressionChildren; + } + } + + private class ClassHeaderExpression extends Expression { + private final Expression annotationS; + private final Expression security; + private final Expression staticS; + private final Expression abstractS; + private final Expression finalS; + private final Expression typeS; + private final Expression nameS; + private final Expression genericS; + private final Expression extendsS; + private final Expression implementsS; + + private ClassHeaderExpression() { + this.annotationS = compileHeaderAnnotation(); + this.security = security().expression(); + this.finalS = Expression.whenWithSpace(isFinal() && !isEnum(), "final"); + this.staticS = Expression.whenWithSpace(isStatic() && !isEnum(), "static"); + this.abstractS = Expression.whenWithSpace(isAbstract() && !isEnum() && !isAnnotation(), "abstract"); + this.typeS = Expression.forType(getKlazz(), typeString(JarClass.this, isEnum())); + this.genericS = Expression.of(compileTypeParameters()); + this.nameS = Expression.of(name()); + this.extendsS = Expression.of(compileHeaderExtends()); + this.implementsS = Expression.of(compileHeaderImplements()); + } + + @Override + public boolean hasChildren() { + return true; + } + + @Override + public List children() { + return Arrays.asList(annotationS, security, staticS, abstractS, finalS, typeS, nameS, genericS, extendsS, implementsS); + } + } +} diff --git a/src/main/java/me/davidsargent/stubjars/components/JarConstructor.java b/src/main/java/davidsar/gent/stubjars/components/JarConstructor.java similarity index 68% rename from src/main/java/me/davidsargent/stubjars/components/JarConstructor.java rename to src/main/java/davidsar/gent/stubjars/components/JarConstructor.java index e28b077..e748c63 100644 --- a/src/main/java/me/davidsargent/stubjars/components/JarConstructor.java +++ b/src/main/java/davidsar/gent/stubjars/components/JarConstructor.java @@ -11,10 +11,10 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars.components; +package davidsar.gent.stubjars.components; -import me.davidsargent.stubjars.Utils; -import me.davidsargent.stubjars.components.writer.JavaClassWriter; +import davidsar.gent.stubjars.Utils; +import davidsar.gent.stubjars.components.writer.Constants; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -22,9 +22,10 @@ import java.lang.reflect.*; import java.util.Arrays; -import static me.davidsargent.stubjars.components.writer.Constants.INDENT; +import static davidsar.gent.stubjars.components.writer.Constants.EMPTY_STRING; +import static davidsar.gent.stubjars.components.writer.Constants.INDENT; -public class JarConstructor extends JarModifers implements CompileableString { +public class JarConstructor extends JarModifiers implements CompileableExpression { private final JarClass klazz; private final Constructor constructor; @@ -48,18 +49,16 @@ boolean shouldIncludeCotr() { @NotNull public String[] parameters() { - Parameter[] parameters = constructor.getParameters(); - return Arrays.stream(parameters) - .map(parameter -> JarType.toString(parameter.getParameterizedType()) + " " + parameter.getName()) + return Arrays.stream(constructor.getParameters()) + .map(parameter -> JarType.toString(parameter.getParameterizedType()) + Constants.SPACE + parameter.getName()) .toArray(String[]::new); } @Nullable - public static Type typeArgumentForClass(@NotNull TypeVariable typeVariable, @NotNull Class klazz) { + private static Type typeArgumentForClass(@NotNull TypeVariable typeVariable, @NotNull Class klazz) { if (klazz.getSuperclass() == null) return null; Type superClassType = klazz.getGenericSuperclass(); if (!(superClassType instanceof ParameterizedType)) return null; - Class superClass = klazz.getSuperclass(); ParameterizedType pSuperClassType = (ParameterizedType) superClassType; Type[] actualTypeParameters = superClass.getTypeParameters(); @@ -100,27 +99,11 @@ private static boolean handleParameterizedType(@NotNull TypeVariable typeVariabl } @NotNull - public String name() { + private String name() { return constructor.getDeclaringClass().getSimpleName(); } - public static boolean hasDefaultConstructor(@NotNull JarClass klazz) { - return hasDefaultConstructor(klazz.getKlazz()); - } - - public boolean isDefaultConstructor() { - return isDefaultConstructor(constructor); - } - - private static boolean isDefaultConstructor(@NotNull Constructor constructor) { - Class klazz = constructor.getDeclaringClass(); - if (klazz.getDeclaringClass() == null || Modifier.isStatic(klazz.getModifiers())) - return constructor.getParameterCount() == 0; - else - return constructor.getParameterCount() == 1 && constructor.getGenericParameterTypes()[0].equals(klazz.getDeclaringClass()); - } - - public static boolean hasDefaultConstructor(@NotNull Class klazz) { + private static boolean hasDefaultConstructor(@NotNull Class klazz) { try { Class declaringClass = klazz.getDeclaringClass(); Constructor declaredConstructor; @@ -135,20 +118,25 @@ public static boolean hasDefaultConstructor(@NotNull Class klazz) { } } - public Constructor getConstructor() { + private Constructor getConstructor() { return constructor; } @Override - public String compileToString() { - final String EMPTY_STRING = ""; + public Expression compileToExpression() { + if (!shouldIncludeCotr()) throw new RuntimeException(); final String security; if (klazz.isInterface()) security = EMPTY_STRING; else - security = security().getModifier() + (security() == SecurityModifier.PACKAGE ? EMPTY_STRING : " "); + security = security().getModifier() + (security() == SecurityModifier.PACKAGE ? EMPTY_STRING : Constants.SPACE); final String nameS = name(); - final String parametersS = Utils.arrayToCommaSeparatedList(parameters(), x -> x); + final String parametersS; + if (canRewriteConstructorParams()) { + parametersS = Constants.EMPTY_STRING; + } else { + parametersS = Utils.arrayToCommaSeparatedList(parameters(), x -> x); + } Class klazzSuperClass = klazz.extendsClass(); final String stubMethod; @@ -158,36 +146,41 @@ public String compileToString() { } else { // We need to call some form of the default constructor, so we can compile code JarConstructor[] declaredConstructors; - declaredConstructors = (JarConstructor[]) JarClass.forClass(klazzSuperClass).constructors().toArray(new JarConstructor[0]); + JarClass jarClass = JarClass.forClass(klazzSuperClass); + declaredConstructors = jarClass.constructors().toArray(new JarConstructor[0]); if (declaredConstructors.length <= 0) throw new UnsupportedOperationException("Cannot infer super cotr to write for " + klazz.getKlazz().getName()); JarConstructor selectedCotr = null; for (JarConstructor declaredCotr : declaredConstructors) { - if (declaredCotr.security() == SecurityModifier.PRIVATE) continue; + if (declaredCotr.security() == SecurityModifier.PRIVATE) { + continue; + } + selectedCotr = declaredCotr; break; } - if (selectedCotr == null) { - stubMethod = "super();"; + if (selectedCotr == null || selectedCotr.canRewriteConstructorParams()) { + stubMethod = Expression.methodCall("super").toString(); } else { Type[] genericParameterTypes = selectedCotr.getConstructor().getGenericParameterTypes(); - stubMethod = String.format("%ssuper(%s);", INDENT, Utils.arrayToCommaSeparatedList( - genericParameterTypes, paramType -> { -// if (!klazz.isStatic() && klazz.isInnerClass() && paramType.equals(klazz.getKlazz().getDeclaringClass())) { -// return null; -// } - return castedDefaultType(paramType, klazz); - } - )); + stubMethod = String.format("%s%s", INDENT, Expression.methodCall("super", + Utils.arrayToCommaSeparatedList(genericParameterTypes, + paramType -> castedDefaultType(paramType, klazz) + )) + ); } } - return String.format("%s%s%s(%s) {\n%s\n}\n\n", '\n', security, nameS, parametersS, stubMethod); + return Expression.of(String.format("%s%s%s%s %s\n\n", Constants.NEW_LINE_CHARACTER, security, nameS, Expression.parenthetical(parametersS), Expression.block(stubMethod))); + } + + boolean canRewriteConstructorParams() { + return security() == SecurityModifier.PRIVATE; } @Nullable - public static String castedDefaultType(Type paramType, JarClass klazz) { + static String castedDefaultType(Type paramType, JarClass klazz) { final Type correctType; if (paramType instanceof TypeVariable) { Type testCorrectType = typeArgumentForClass((TypeVariable) paramType, klazz.getKlazz()); @@ -201,9 +194,9 @@ public static String castedDefaultType(Type paramType, JarClass klazz) { } if (correctType.equals(Void.class)) return null; - return String.format("(%s) %s", JarType.toString(correctType, true, type -> { + return Expression.cast(JarType.toString(correctType, true, type -> { Type obj = typeArgumentForClass(type, klazz.getKlazz()); return JarType.toString(obj != null ? obj : type); - }), JavaClassWriter.defaultValueForType(correctType)); + }), Value.defaultValueForType(correctType)); } } diff --git a/src/main/java/me/davidsargent/stubjars/components/JarField.java b/src/main/java/davidsar/gent/stubjars/components/JarField.java similarity index 50% rename from src/main/java/me/davidsargent/stubjars/components/JarField.java rename to src/main/java/davidsar/gent/stubjars/components/JarField.java index 08bbf8f..ccafadd 100644 --- a/src/main/java/me/davidsargent/stubjars/components/JarField.java +++ b/src/main/java/davidsar/gent/stubjars/components/JarField.java @@ -11,17 +11,12 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars.components; - -import me.davidsargent.stubjars.components.writer.JavaClassWriter; +package davidsar.gent.stubjars.components; import java.lang.reflect.Field; import java.lang.reflect.Type; -import static me.davidsargent.stubjars.components.writer.Constants.EMPTY_STRING; - - -public class JarField extends JarModifers implements CompileableString { +public class JarField extends JarModifiers implements CompileableExpression { private final JarClass jarClass; private final Field field; @@ -39,33 +34,29 @@ public String name() { return field.getName(); } - public Class returnType() { - return field.getType(); - } - - public Type genericReturnType() { + private Type genericReturnType() { return field.getGenericType(); } @Override - public String compileToString() { + public Expression compileToExpression() { // Figure method signature - final String security = security().getModifier() + (security() == SecurityModifier.PACKAGE ? EMPTY_STRING : " "); - final String finalS = isFinal() ? "final " : EMPTY_STRING; - final String staticS = isStatic() ? "static " : EMPTY_STRING; - final String volatileS = isVolatile() ? "volatile " : EMPTY_STRING; - final String transientS = isTransient() ? "transient " : EMPTY_STRING; - final String returnTypeS = JarType.toString(genericReturnType()); - final String nameS = name(); - - final String assignmentS; + final Expression security = Expression.of(Expression.of(security().getModifier()), Expression.when(security() != SecurityModifier.PACKAGE, Expression.StringExpression.SPACE)); + final Expression finalS = Expression.whenWithSpace(isFinal(), "final"); + final Expression staticS = Expression.whenWithSpace(isStatic(), "static"); + final Expression volatileS = Expression.whenWithSpace(isVolatile(), "volatile"); + final Expression transientS = Expression.whenWithSpace(isTransient(), "transient"); + final Expression returnTypeS = Expression.of(JarType.toString(genericReturnType())); + final Expression nameS = Expression.of(name()); + + final Expression assignmentS; if (isFinal()) { - assignmentS = String.format(" = %s", JavaClassWriter.defaultValueForType(genericReturnType())); + assignmentS = Expression.of(Expression.of(" = "), Expression.forType(genericReturnType(), Value.defaultValueForType(genericReturnType()))); } else { - assignmentS = EMPTY_STRING; + assignmentS = Expression.StringExpression.EMPTY; } - return String.format("%s%s%s%s%s%s %s%s;", security, finalS, staticS, volatileS, transientS, returnTypeS, nameS, assignmentS); + return Expression.of(Expression.of(security, finalS, staticS, volatileS, transientS, returnTypeS).spaceAfter(), nameS, assignmentS).statement(); } public JarClass getClazz() { diff --git a/src/main/java/davidsar/gent/stubjars/components/JarMethod.java b/src/main/java/davidsar/gent/stubjars/components/JarMethod.java new file mode 100644 index 0000000..65f4db1 --- /dev/null +++ b/src/main/java/davidsar/gent/stubjars/components/JarMethod.java @@ -0,0 +1,196 @@ +/* + * Copyright 2018 David Sargent + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package davidsar.gent.stubjars.components; + +import davidsar.gent.stubjars.Utils; +import davidsar.gent.stubjars.components.writer.Constants; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.*; +import java.util.Arrays; +import java.util.Set; + +public class JarMethod extends JarModifiers implements CompileableExpression { + private final JarClass parentClazz; + private final Method method; + private String[] cachedParameters; + + JarMethod(@NotNull JarClass parentClazz, @NotNull Method method) { + this.parentClazz = parentClazz; + this.method = method; + } + + @Override + protected int getModifiers() { + return method.getModifiers(); + } + + private String name() { + return method.getName(); + } + + @NotNull + private String[] parameters() { + if (cachedParameters != null) return cachedParameters; + Parameter[] parameters = method.getParameters(); + String[] stringifiedParameters = Arrays.stream(parameters) + .map(parameter -> JarType.toString(parameter.getParameterizedType()) + Constants.SPACE + parameter.getName()) + .toArray(String[]::new); + + if (method.isVarArgs()) { + Parameter varArgsParameter = parameters[parameters.length - 1]; + Type parameterizedType = varArgsParameter.getParameterizedType(); + if (JarType.isArray(parameterizedType)) { + if (parameterizedType instanceof GenericArrayType) { + stringifiedParameters[parameters.length - 1] = JarType.toString(((GenericArrayType) parameterizedType).getGenericComponentType()) + "... " + varArgsParameter.getName(); + } else if (parameterizedType instanceof Class) { + stringifiedParameters[parameters.length - 1] = JarType.toString(((Class) parameterizedType).getComponentType()) + "... " + varArgsParameter.getName(); + } + } + } + + cachedParameters = stringifiedParameters; + return stringifiedParameters; + } + + private Expression buildMethod(boolean isEnumField) { + // Skip create methods for these types of things, enum fields can't have static methods + if ((isEnumField || parentClazz.isInterface()) && isStatic()) return Expression.StringExpression.EMPTY; + // Check if the enum method we are about to write could actually exist + if (isEnumField) { + // todo: fix this issue since Enum members may have static classes + if (isFinal()) + return Expression.StringExpression.EMPTY; + Class declaringClass = parentClazz.getKlazz().getDeclaringClass(); + if (declaringClass != null) { + try { + declaringClass.getDeclaredMethod(name(), parameterTypes()); + return Expression.StringExpression.EMPTY; + } catch (NoSuchMethodException ignored) { + } + } + } + + // Figure method signature + final Expression security; + if (parentClazz.isInterface()) { + security = Expression.StringExpression.EMPTY; + } else { + security = Expression.of(Expression.of(security().getModifier()), Expression.whenWithSpace(security() != SecurityModifier.PACKAGE, Constants.SPACE)); + } + final Expression finalS = Expression.whenWithSpace(isFinal(), "final"); + final Expression staticS = Expression.whenWithSpace(isStatic(), "static"); + final Expression abstractS; + if (parentClazz.isInterface()) { + abstractS = Expression.StringExpression.EMPTY; + } else { + abstractS = Expression.whenWithSpace(isAbstract(), "abstract"); + } + final Expression returnTypeS = Expression.forType(genericReturnType(), JarType.toString(genericReturnType())); + final Expression nameS = Expression.of(name()); + final Expression parametersS = Expression.of(Utils.arrayToCommaSeparatedList(parameters(), x -> x)); + final Expression throwsS = Expression.of(requiresThrowsSignature() ? " throws " + Utils.arrayToCommaSeparatedList(throwsTypes(), JarType::toString) : Constants.EMPTY_STRING); + final Expression genericS; + TypeVariable[] typeParameters = typeParameters(); + genericS = Expression.of(JarType.convertTypeParametersToString(typeParameters)); + + // What should the method body be? + final Expression stubMethod; + final Type returnType = genericReturnType(); + if (returnType.equals(void.class)) { + stubMethod = Expression.block(); + } else { + stubMethod = Expression.block(Expression.of(Expression.spaceAfter("return"), Expression.forType(returnType, JarConstructor.castedDefaultType(returnType, parentClazz))).statement()); + } + + // Finally, put all of the pieces together + Expression methodHeader = Expression.of(Expression.StringExpression.NEW_LINE, security, finalS, staticS, abstractS, genericS, returnTypeS, Expression.StringExpression.SPACE, nameS, Expression.parenthetical(parametersS), throwsS); + Expression methodBody; + if (parentClazz.isAnnotation() && hasDefaultValue()) { + methodBody = Expression.of(Expression.of(" default "), Expression.forType(defaultValue().getClass(), Value.defaultValueForType(defaultValue().getClass(), true)), Expression.StringExpression.SEMICOLON); + } else if (isAbstract() || (parentClazz.isInterface() && !isStatic())) { + methodBody = Expression.StringExpression.SEMICOLON; + } else { + methodBody = stubMethod; + } + + return Expression.of(methodHeader, Expression.StringExpression.SPACE, methodBody); + } + + boolean isSynthetic() { + return method.isSynthetic(); + } + + boolean shouldIncludeStaticMethod() { + if (!isStatic()) return shouldIncludeMethod(); + JarClass klazz = JarClass.forClass(method.getDeclaringClass()); + if (klazz.isEnum()) { + String name = method.getName(); + if (name.equals("values") || name.equals("valueOf")) { + return false; + } + } + + Set jarClasses = klazz.allSuperClassesAndInterfaces(); + long count = jarClasses.stream() + .filter(x -> x.hasMethod(method)) + .count(); + return count == 0 && shouldIncludeMethod(); + } + + private boolean shouldIncludeMethod() { + for (Class paramType : method.getParameterTypes()) { + if (!JarClass.hasSafeName(paramType)) return false; + } + + return JarClass.hasSafeName(method.getReturnType()); + } + + private Type genericReturnType() { + return method.getGenericReturnType(); + } + + private TypeVariable[] typeParameters() { + return method.getTypeParameters(); + } + + private Class[] parameterTypes() { + return method.getParameterTypes(); + } + + private boolean hasDefaultValue() { + return defaultValue() != null; + } + + private Object defaultValue() { + return method.getDefaultValue(); + } + + private Type[] throwsTypes() { + return method.getGenericExceptionTypes(); + } + + private boolean requiresThrowsSignature() { + return throwsTypes().length > 0; + } + + @Override + public Expression compileToExpression() { + return buildMethod(false); + } + + Expression compileToString(boolean isEnumField) { + return buildMethod(isEnumField); + } +} diff --git a/src/main/java/me/davidsargent/stubjars/components/JarModifers.java b/src/main/java/davidsar/gent/stubjars/components/JarModifiers.java similarity index 85% rename from src/main/java/me/davidsargent/stubjars/components/JarModifers.java rename to src/main/java/davidsar/gent/stubjars/components/JarModifiers.java index 8216ef4..4593423 100644 --- a/src/main/java/me/davidsargent/stubjars/components/JarModifers.java +++ b/src/main/java/davidsar/gent/stubjars/components/JarModifiers.java @@ -11,11 +11,11 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars.components; +package davidsar.gent.stubjars.components; import java.lang.reflect.Modifier; -public abstract class JarModifers { +public abstract class JarModifiers { protected abstract int getModifiers(); public SecurityModifier security() { int modifiers = getModifiers(); @@ -30,23 +30,23 @@ public SecurityModifier security() { } } - public boolean isFinal() { + boolean isFinal() { return Modifier.isFinal(getModifiers()); } - public boolean isStatic() { + boolean isStatic() { return Modifier.isStatic(getModifiers()); } - public boolean isAbstract() { + boolean isAbstract() { return Modifier.isAbstract(getModifiers()); } - public boolean isVolatile() { + boolean isVolatile() { return Modifier.isVolatile(getModifiers()); } - public boolean isTransient() { + boolean isTransient() { return Modifier.isTransient(getModifiers()); } } diff --git a/src/main/java/me/davidsargent/stubjars/components/JarType.java b/src/main/java/davidsar/gent/stubjars/components/JarType.java similarity index 80% rename from src/main/java/me/davidsargent/stubjars/components/JarType.java rename to src/main/java/davidsar/gent/stubjars/components/JarType.java index 3d9a32a..f981750 100644 --- a/src/main/java/me/davidsargent/stubjars/components/JarType.java +++ b/src/main/java/davidsar/gent/stubjars/components/JarType.java @@ -11,8 +11,10 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars.components; +package davidsar.gent.stubjars.components; +import davidsar.gent.stubjars.Utils; +import davidsar.gent.stubjars.components.writer.Constants; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,31 +23,29 @@ import java.util.function.Function; import java.util.stream.Collectors; -public class JarType { +class JarType { private final Type type; - private final boolean isGeneric; public JarType(@NotNull Type type) { this.type = type; - isGeneric = type instanceof ParameterizedType; } - private boolean isGeneric() { - return isGeneric; - } - - @Nullable - public Class getOwnerClass() { - return isGeneric() ? ((Class) getParameterizedType().getOwnerType()) : (Class) type; - } - - public Type getRawType() { - return isGeneric() ? getParameterizedType().getRawType() : type; - } + static String convertTypeParametersToString(TypeVariable[] typeParameters) { + String genericS; + if (typeParameters.length == 0) { + genericS = Constants.SPACE; + } else { + String typeParams = Utils.arrayToCommaSeparatedList(typeParameters, typeParam -> { + if (typeParam.getBounds()[0] == Object.class) { + return typeParam.getName(); + } else { + return typeParam.getName() + " extends " + JarType.toString(typeParam.getBounds()[0]); + } + }); + genericS = "<" + typeParams + "> "; + } - @NotNull - private ParameterizedType getParameterizedType() { - return (ParameterizedType) type; + return genericS; } @NotNull @@ -55,12 +55,12 @@ public String toString() { } @NotNull - public static String toString(@NotNull Type type) { + static String toString(@NotNull Type type) { return toString(type, false, null); } @NotNull - public static String toString(@NotNull Type type, boolean keepSimple, @Nullable Function resolver) { + static String toString(@NotNull Type type, boolean keepSimple, @Nullable Function resolver) { if (type instanceof Class) { return JarClass.safeFullNameForClass((Class) type); } @@ -77,7 +77,7 @@ public static String toString(@NotNull Type type, boolean keepSimple, @Nullable builder.append("."); Class rawTypeOfOwner = (Class) ((ParameterizedType) pType.getOwnerType()).getRawType(); String rawType = ((Class) pType.getRawType()).getName() - .replace(rawTypeOfOwner.getName() + "$", ""); + .replace(rawTypeOfOwner.getName() + "$", Constants.EMPTY_STRING); builder.append(rawType); } else { throw new UnsupportedOperationException(type.getClass().getName()); @@ -127,11 +127,10 @@ public static String toString(@NotNull Type type, boolean keepSimple, @Nullable } } - // debug return type.toString(); throw new UnsupportedOperationException(type.getClass().getName()); } - public static boolean isArray(Type parameterizedType) { + static boolean isArray(Type parameterizedType) { return parameterizedType instanceof GenericArrayType || (parameterizedType instanceof Class && ((Class) parameterizedType).isArray()); } } diff --git a/src/main/java/me/davidsargent/stubjars/components/SecurityModifier.java b/src/main/java/davidsar/gent/stubjars/components/SecurityModifier.java similarity index 67% rename from src/main/java/me/davidsargent/stubjars/components/SecurityModifier.java rename to src/main/java/davidsar/gent/stubjars/components/SecurityModifier.java index 72a4751..9baf9f4 100644 --- a/src/main/java/me/davidsargent/stubjars/components/SecurityModifier.java +++ b/src/main/java/davidsar/gent/stubjars/components/SecurityModifier.java @@ -11,10 +11,12 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars.components; +package davidsar.gent.stubjars.components; + +import davidsar.gent.stubjars.components.writer.Constants; public enum SecurityModifier { - PRIVATE("private"), PROTECTED("protected"), PACKAGE(""), PUBLIC("public"); + PRIVATE("private"), PROTECTED("protected"), PACKAGE(Constants.EMPTY_STRING), PUBLIC("public"); private final String modifier; SecurityModifier(String modifier) { @@ -24,4 +26,8 @@ public enum SecurityModifier { public String getModifier() { return modifier; } + + public Expression expression() { + return Expression.of(Expression.of(getModifier()), Expression.when(this != SecurityModifier.PACKAGE, Expression.StringExpression.SPACE)); + } } diff --git a/src/main/java/davidsar/gent/stubjars/components/Value.java b/src/main/java/davidsar/gent/stubjars/components/Value.java new file mode 100644 index 0000000..a95ab63 --- /dev/null +++ b/src/main/java/davidsar/gent/stubjars/components/Value.java @@ -0,0 +1,80 @@ +/* + * Copyright 2018 David Sargent + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package davidsar.gent.stubjars.components; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +class Value { + /** + * Returns a String contains the default value for a given type + * + * @param type a {@link Type} to get the default value for + * @return a String with the default type for the parameter type + */ + public static String defaultValueForType(Type type) { + return defaultValueForType(type, false); + } + + /** + * Returns a String contains the default value for a given type + * + * @param type a {@link Type} to get the default value for + * @param constant {@code true} if the returned value should be a constant + * @return a String with the default type for the parameter type + */ + public static String defaultValueForType(Type type, boolean constant) { + if (!(type instanceof Class)) { + if (type instanceof ParameterizedType) return defaultValueForType(((ParameterizedType) type).getRawType()); + return defaultValueForType(Object.class); + } + + if (type.equals(int.class) || type.equals(Integer.class)) { + return "0"; + } else if (type.equals(double.class) || type.equals(Double.class)) { + return "0.0"; + } else if (type.equals(long.class) || type.equals(Long.class)) { + return "0L"; + } else if (type.equals(byte.class) || type.equals(Byte.class)) { + return Expression.cast(byte.class, 0); + } else if (type.equals(short.class) || type.equals(Short.class)) { + return Expression.cast(short.class, 0); + } else if (type.equals(boolean.class) || type.equals(Boolean.class)) { + return Boolean.toString(false); + } else if (type.equals(float.class) || type.equals(Float.class)) { + return Expression.cast(float.class, 0); + } else if (type.equals(char.class) || type.equals(Character.class)) { + return Expression.cast(char.class, 0); + } else if (type.equals(String.class)) { + return "\"\""; + } else if (((Class) type).isArray()) { + if (constant) + return "{}"; + else + return String.format("new %s {}", JarType.toString(type)); + } else if (((Class) type).isEnum()) { + //noinspection unchecked + Enum[] enumConstants = JarClass.getEnumConstantsFor((Class) type); + if (enumConstants == null) { + if (constant) throw new RuntimeException("Cannot determine constant value!"); + + return "null"; + } + + return JarType.toString(type) + "." + enumConstants[0].name(); + } else { + return "null"; + } + } +} diff --git a/src/main/java/me/davidsargent/stubjars/components/writer/Constants.java b/src/main/java/davidsar/gent/stubjars/components/writer/Constants.java similarity index 58% rename from src/main/java/me/davidsargent/stubjars/components/writer/Constants.java rename to src/main/java/davidsar/gent/stubjars/components/writer/Constants.java index 927b27b..aadf489 100644 --- a/src/main/java/me/davidsargent/stubjars/components/writer/Constants.java +++ b/src/main/java/davidsar/gent/stubjars/components/writer/Constants.java @@ -11,9 +11,20 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars.components.writer; +package davidsar.gent.stubjars.components.writer; public class Constants { public static final String EMPTY_STRING = ""; + public static final String SPACE = " "; + public static final String SEMICOLON = ";"; public static final String INDENT = " "; + public final static String NEW_LINE_CHARACTER = "\n"; + + public final static String LEFT_PAREN = "("; + public final static String RIGHT_PAREN = ")"; + public static final String LCURLY = "{"; + public static final String RCURLY = "}"; + public static final String COMMA = ","; + public static final String AT = "@"; + public static final String PERIOD = "."; } diff --git a/src/main/java/davidsar/gent/stubjars/components/writer/JavaClassWriter.java b/src/main/java/davidsar/gent/stubjars/components/writer/JavaClassWriter.java new file mode 100644 index 0000000..68b695e --- /dev/null +++ b/src/main/java/davidsar/gent/stubjars/components/writer/JavaClassWriter.java @@ -0,0 +1,71 @@ +/* + * Copyright 2018 David Sargent + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package davidsar.gent.stubjars.components.writer; + +import davidsar.gent.stubjars.Preconditions; +import davidsar.gent.stubjars.components.Expression; +import davidsar.gent.stubjars.components.JarClass; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +public class JavaClassWriter extends Writer { + private final JarClass klazz; + private String compiledString; + + public JavaClassWriter(@NotNull final File file, @NotNull final JarClass klazz, @NotNull WriterThread writerThread) { + super(file, writerThread); + Preconditions.checkNotNull(file); + Preconditions.checkNotNull(klazz); + this.klazz = klazz; + } + + @NotNull + private String compile() { + if (compiledString == null) { + this.compiledString = compile(klazz) + .replaceAll("(\\s*\\n )+\\s*\\n(\\s*)", "\n\n$2") + .replaceAll("([^ \\t\\n])[\\t ]+([^ \\t])", "$1 $2"); + } + + return compiledString; + } + + @NotNull + private static String compile(@NotNull final JarClass klazz) { + String packageStatement = compilePackageStatement(klazz); + Expression classBody = compileClass(klazz); + return String.format("%s\n%s", packageStatement, classBody); + } + + @NotNull + private static Expression compileClass(@NotNull final JarClass klazz) { + return klazz.compileToExpression(); + } + + /** + * Produces a String containing a source code version of the package name declaration + * + * @param klazz the {@link JarClass} to create the declaration for + * @return source code version of the package name declaration + */ + @NotNull + private static String compilePackageStatement(@NotNull final JarClass klazz) { + return String.format("package %s;\n", klazz.packageName()); + } + + public void write() { + writeDataWithDedicatedThread(compile()); + } +} diff --git a/src/main/java/me/davidsargent/stubjars/components/writer/Writer.java b/src/main/java/davidsar/gent/stubjars/components/writer/Writer.java similarity index 92% rename from src/main/java/me/davidsargent/stubjars/components/writer/Writer.java rename to src/main/java/davidsar/gent/stubjars/components/writer/Writer.java index 921a2d5..4c7a66d 100644 --- a/src/main/java/me/davidsargent/stubjars/components/writer/Writer.java +++ b/src/main/java/davidsar/gent/stubjars/components/writer/Writer.java @@ -11,11 +11,13 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars.components.writer; +package davidsar.gent.stubjars.components.writer; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.BufferedWriter; import java.io.File; @@ -23,6 +25,7 @@ import java.nio.file.Files; public class Writer { + private static final Logger log = LoggerFactory.getLogger(Writer.class); private final File file; private volatile String dataCache; private final WriterThread writerThread; @@ -90,7 +93,7 @@ synchronized void _threadWrite() { try { write(dataCache); } catch (IOException e) { - e.printStackTrace(); + log.error("Encountered an error writing to file", e); } dataCache = null; } diff --git a/src/main/java/me/davidsargent/stubjars/components/writer/WriterThread.java b/src/main/java/davidsar/gent/stubjars/components/writer/WriterThread.java similarity index 90% rename from src/main/java/me/davidsargent/stubjars/components/writer/WriterThread.java rename to src/main/java/davidsar/gent/stubjars/components/writer/WriterThread.java index 9ff3a27..3f9d024 100644 --- a/src/main/java/me/davidsargent/stubjars/components/writer/WriterThread.java +++ b/src/main/java/davidsar/gent/stubjars/components/writer/WriterThread.java @@ -11,10 +11,9 @@ * License for the specific language governing permissions and limitations under the License. */ -package me.davidsargent.stubjars.components.writer; +package davidsar.gent.stubjars.components.writer; import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.Semaphore; /** * @see Writer @@ -33,15 +32,11 @@ public void done() { stop = true; } - public void kill() { - runningThread.interrupt(); - } - void addWriter(Writer writer) { try { writersToProcess.put(writer); } catch (InterruptedException e) { - e.printStackTrace(); + Thread.currentThread().interrupt(); } } diff --git a/src/main/java/davidsar/gent/stubjars/utils/Streams.java b/src/main/java/davidsar/gent/stubjars/utils/Streams.java new file mode 100644 index 0000000..ab4857c --- /dev/null +++ b/src/main/java/davidsar/gent/stubjars/utils/Streams.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018 David Sargent + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package davidsar.gent.stubjars.utils; + +import org.jetbrains.annotations.NotNull; + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class Streams { + /** + * @param e the {@link Enumeration} to back to {@link Stream} + * @param type of objects contained in the {@code Enumeration} + * @return a new {@code Stream } from the elements of the {@code Enumeration} + * @implNote from https://stackoverflow.com/questions/33242577/how-do-i-turn-a-java-enumeration-into-a-stream + */ + @NotNull + public static Stream makeFor(@NotNull Enumeration e) { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize( + new Iterator() { + @Override + public T next() { + return e.nextElement(); + } + + @Override + public boolean hasNext() { + return e.hasMoreElements(); + } + }, + Spliterator.ORDERED), false); + } +} \ No newline at end of file diff --git a/src/main/java/me/davidsargent/stubjars/JarFile.java b/src/main/java/me/davidsargent/stubjars/JarFile.java deleted file mode 100644 index 808d969..0000000 --- a/src/main/java/me/davidsargent/stubjars/JarFile.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2018 David Sargent - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under the License. - */ - -package me.davidsargent.stubjars; - -import me.davidsargent.stubjars.components.JarClass; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.*; -import java.util.jar.JarEntry; - -public class JarFile { - private static Map jarFiles = new HashMap<>(); - private final File jar; - private ClassLoader classLoader; - - private JarFile(File jar) { - this.jar = jar; - } - - public static JarFile forFile(File jar) { - if (jarFiles.containsKey(jar)) return jarFiles.get(jar); - - JarFile jarFile = new JarFile(jar); - jarFiles.put(jar, jarFile); - return jarFile; - } - - public static ClassLoader createClassLoaderFromJars(@Nullable ClassLoader parentClassLoader, JarFile... jarFiles) { - URL[] urls = new URL[jarFiles.length]; - for (int i = 0; i < jarFiles.length; ++i) { - urls[i] = jarFiles[i].getUrl(); - } - - return new URLClassLoader(urls, parentClassLoader == null ? JarFile.class.getClassLoader() : parentClassLoader); - } - - public ClassLoader classLoader() { - if (classLoader != null) return classLoader; - - classLoader = new URLClassLoader(new URL[]{getUrl()}, this.getClass().getClassLoader()); - return classLoader; - } - - private URL getUrl() { - try { - return jar.toURI().toURL(); - } catch (MalformedURLException e) { - throw new RuntimeException(String.format("Could not create classloader for \"%s\"", jar.getAbsolutePath()), e); - } - } - - public Set> getClasses(ClassLoader loader) throws IOException, ClassNotFoundException { - java.util.jar.JarFile iJar = new java.util.jar.JarFile(jar); - Enumeration entries = iJar.entries(); - Set> jarClasses = new HashSet<>(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String name = entry.getName(); - if (!name.endsWith(".class")) continue; - JarClass jarClass = new JarClass(loader, this, name); - findInnerClasses(jarClasses, jarClass); - jarClasses.add(jarClass); - } - - return jarClasses; - } - - private void findInnerClasses(Set> jarClasses, JarClass jarClass) { - for (JarClass innerKlazz : jarClass.innerClasses()) { - jarClasses.add(innerKlazz); - findInnerClasses(jarClasses, innerKlazz); - } - } -} diff --git a/src/main/java/me/davidsargent/stubjars/components/JarClass.java b/src/main/java/me/davidsargent/stubjars/components/JarClass.java deleted file mode 100644 index ffbbf09..0000000 --- a/src/main/java/me/davidsargent/stubjars/components/JarClass.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2018 David Sargent - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under the License. - */ - -package me.davidsargent.stubjars.components; - -import me.davidsargent.stubjars.JarFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class JarClass extends JarModifers { - private final static Pattern classEntryPatternToBeStripped = Pattern.compile("\\.class$"); - private static Map, JarClass> classToJarClassMap; - private final ClassLoader classLoader; - private final Class klazz; - private final JarFile jarFile; - private final String entryName; - - public JarClass(@NotNull ClassLoader classLoader, JarFile jarFile, @NotNull String entryName) throws ClassNotFoundException { - this.classLoader = classLoader; - this.jarFile = jarFile; - this.entryName = entryName; - - String convertedName = convertEntryNameToClassName(entryName); - klazz = (Class) Class.forName(convertedName, false, classLoader); - } - - private JarClass(ClassLoader classLoader, JarFile jarFile, String entryName, @NotNull Class klazz) throws ClassNotFoundException { - this.classLoader = classLoader; - this.jarFile = jarFile; - this.entryName = entryName; - - this.klazz = klazz; - } - - public static void loadClassToJarClassMap(@NotNull Map, @NotNull JarClass> map) { - classToJarClassMap = map; - } - - @NotNull - public static JarClass forClass(@NotNull Class klazz) { - if (classToJarClassMap != null && classToJarClassMap.containsKey(klazz)) return classToJarClassMap.get(klazz); - - try { - return new JarClass<>(klazz.getClassLoader(), null, null, klazz); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - - @NotNull - private static String convertEntryNameToClassName(@NotNull String entryName) { - Matcher matcher = classEntryPatternToBeStripped.matcher(entryName); - if (matcher.find()) entryName = matcher.replaceAll(""); - return entryName.replace('/', '.'); - } - - public boolean isEnum() { - Class extendClazz = extendsClass(); - return klazz.isEnum(); - } - - @NotNull - public Class getKlazz() { - return klazz; - } - - public boolean isInnerClass() { - return klazz.getDeclaringClass() != null || klazz.isLocalClass() || klazz.isAnonymousClass() ; - } - - @NotNull - public String name() { - return klazz.getSimpleName(); - } - - @NotNull - public String fullName() { - return safeFullNameForClass(klazz); - } - - public static boolean hasSafeName(@NotNull Class klazz) { - JarClass jarClass = JarClass.forClass(klazz); - return !jarClass.klazz.isSynthetic() && !jarClass.klazz.isAnonymousClass(); - } - - @NotNull - public static String safeFullNameForClass(@NotNull Class klazz) { - if (!hasSafeName(klazz)) - throw new IllegalArgumentException("Class does not have safe name."); - if (klazz.isArray()) - return safeFullNameForClass(klazz.getComponentType()) + "[]"; - String s = klazz.getName().replaceAll("\\$\\d*", "."); - if (s.endsWith(".")) - throw new IllegalArgumentException("Class does not have safe name."); - return s; - } - - @NotNull - public String packageName() { - return klazz.getPackage().getName(); - } - - @Nullable - public Class extendsClass() { - Class superclass = klazz.getSuperclass(); - return superclass == Object.class ? null : superclass; - } - - @NotNull - public Class[] implementsInterfaces() { - return klazz.getInterfaces(); - } - - @NotNull - public Type[] implementsGenericInterfaces() { - return klazz.getGenericInterfaces(); - } - - public boolean isInterface() { - return klazz.isInterface(); - } - - public boolean isAnnotation() { - return klazz.isAnnotation(); - } - - @Override - protected int getModifiers() { - return klazz.getModifiers(); - } - - public Set fields() { - return Arrays.stream(klazz.getDeclaredFields()) - .map(field -> new JarField(this, field)) - .filter(field -> field.security() != SecurityModifier.PRIVATE) - .collect(Collectors.toSet()); - } - - @NotNull - public Set> innerClasses() { - Stream> innerClassesStream = Arrays.stream(klazz.getDeclaredClasses()) - .filter(klazz -> !klazz.isLocalClass()) - .filter(klazz -> !klazz.isAnonymousClass()); - - return innerClassesStream.map(JarClass::forClass).collect(Collectors.toSet()); - } - - @NotNull - public Set methods() { - return Arrays.stream(klazz.getDeclaredMethods()) - .map(JarMethod::new) - .filter(method -> method.security() != SecurityModifier.PRIVATE) - .filter(method -> !method.isSynthetic()) - .filter(JarMethod::shouldIncludeStaticMethod) - .collect(Collectors.toSet()); - } - - @Override - public boolean equals(Object o) { - return o instanceof JarClass && klazz.equals(((JarClass) o).klazz); - } - - @Override - public int hashCode() { - return klazz.hashCode(); - } - - @NotNull - public Set allSuperClassesAndInterfaces() { - return allSuperClassesAndInterfaces(klazz).stream().map(JarClass::forClass).collect(Collectors.toSet()); - } - - @NotNull - private static Set> allSuperClassesAndInterfaces(@NotNull Class klazz) { - Set> superClasses = allSuperClasses(klazz, new HashSet<>()); - Set> interfaces = new HashSet<>(); - Collections.addAll(interfaces, klazz.getInterfaces()); - for (Class kInterface : superClasses) { - Collections.addAll(interfaces, kInterface.getInterfaces()); - } - - superClasses.addAll(interfaces); - return superClasses; - } - - @NotNull - private static Set> allSuperClasses(@NotNull Class klazz, @NotNull Set> superClasses) { - Class superClass = klazz.getSuperclass(); - if (superClass == null ) - return superClasses; - superClasses.add(superClass); - return allSuperClasses(superClass, superClasses); - } - - boolean hasMethod(@NotNull Method method) { - try { - Method declaredMethodSuper = klazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); - return true; - } catch (NoSuchMethodException e) { - return false; - } - } - - @NotNull - public Type extendsGenericClass() { - return klazz.getGenericSuperclass(); - } - - @NotNull - public Set constructors() { - //noinspection unchecked - return Arrays.stream(klazz.getDeclaredConstructors()) - .map(x -> new JarConstructor(this, x)) - // .filter(cotr -> cotr.security() != SecurityModifier.PRIVATE) - .filter(JarConstructor::shouldIncludeCotr) - .collect(Collectors.toSet()); - } -} diff --git a/src/main/java/me/davidsargent/stubjars/components/JarMethod.java b/src/main/java/me/davidsargent/stubjars/components/JarMethod.java deleted file mode 100644 index cc27c71..0000000 --- a/src/main/java/me/davidsargent/stubjars/components/JarMethod.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2018 David Sargent - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under the License. - */ - -package me.davidsargent.stubjars.components; - -import org.jetbrains.annotations.NotNull; - -import java.lang.reflect.*; -import java.util.Arrays; -import java.util.Set; - -public class JarMethod extends JarModifers { - private final Method method; - private String[] cachedParameters; - - public JarMethod(Method method) { - this.method = method; - } - - @Override - protected int getModifiers() { - return method.getModifiers(); - } - - public String name() { - return method.getName(); - } - - public Class returnType() { - return method.getReturnType(); - } - - public boolean isVarArg() { - return method.isVarArgs(); - } - - @NotNull - public String[] parameters() { - if (cachedParameters != null) return cachedParameters; - Parameter[] parameters = method.getParameters(); - String[] stringifiedParameters = Arrays.stream(parameters) - .map(parameter -> JarType.toString(parameter.getParameterizedType()) + " " + parameter.getName()) - .toArray(String[]::new); - - if (method.isVarArgs()) { - Parameter varArgsParameter = parameters[parameters.length - 1]; - Type parameterizedType = varArgsParameter.getParameterizedType(); - if (JarType.isArray(parameterizedType)) { - if (parameterizedType instanceof GenericArrayType) { - stringifiedParameters[parameters.length - 1] = JarType.toString(((GenericArrayType) parameterizedType).getGenericComponentType()) + "... " + varArgsParameter.getName(); - } else if (parameterizedType instanceof Class) { - stringifiedParameters[parameters.length - 1] = JarType.toString(((Class) parameterizedType).getComponentType()) + "... " + varArgsParameter.getName(); - } - } - } - - cachedParameters = stringifiedParameters; - return stringifiedParameters; - } - - public boolean isSynthetic() { - return method.isSynthetic(); - } - - public boolean shouldIncludeStaticMethod() { - if (!isStatic()) return shouldIncludeMethod(); - JarClass klazz = JarClass.forClass(method.getDeclaringClass()); - if (klazz.isEnum()) { - String name = method.getName(); - if (name.equals("values") || name.equals("valueOf")) { - return false; - } - } - - Set jarClasses = klazz.allSuperClassesAndInterfaces(); - long count = jarClasses.stream() - .filter(x -> x.hasMethod(method)) - .count(); - return count == 0 && shouldIncludeMethod(); - } - - private boolean shouldIncludeMethod() { - for (Class paramType : method.getParameterTypes()) { - if (!JarClass.hasSafeName(paramType)) return false; - } - - return JarClass.hasSafeName(method.getReturnType()); - } - - public Type genericReturnType() { - return method.getGenericReturnType(); - } - - public TypeVariable[] typeParameters() { - return method.getTypeParameters(); - } - - public Class[] parameterTypes() { - return method.getParameterTypes(); - } - - public boolean hasDefaultValue() { - return defaultValue() != null; - } - - public Object defaultValue() { - return method.getDefaultValue(); - } - - public Type[] throwsTypes() { - return method.getGenericExceptionTypes(); - } - - public boolean requiresThrowsSignature() { - return throwsTypes().length > 0; - } -} diff --git a/src/main/java/me/davidsargent/stubjars/components/writer/JavaClassWriter.java b/src/main/java/me/davidsargent/stubjars/components/writer/JavaClassWriter.java deleted file mode 100644 index dc1a497..0000000 --- a/src/main/java/me/davidsargent/stubjars/components/writer/JavaClassWriter.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright 2018 David Sargent - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under the License. - */ - -package me.davidsargent.stubjars.components.writer; - -import me.davidsargent.stubjars.Preconditions; -import me.davidsargent.stubjars.Utils; -import me.davidsargent.stubjars.components.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.lang.annotation.Annotation; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.*; -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import static me.davidsargent.stubjars.components.writer.Constants.EMPTY_STRING; -import static me.davidsargent.stubjars.components.writer.Constants.INDENT; - -public class JavaClassWriter extends Writer { - private final JarClass klazz; - private String compiledString; - - public JavaClassWriter(@NotNull final File file, @NotNull final JarClass klazz, @NotNull WriterThread writerThread) { - super(file, writerThread); - Preconditions.checkNotNull(file); - Preconditions.checkNotNull(klazz); - this.klazz = klazz; - } - - private String compile() { - if (compiledString == null) { - String compiledStringA = compile(klazz); - this.compiledString = compiledStringA - .replaceAll("(\\s*\\n )+\\s*\\n(\\s*)", "\n\n$2") - .replaceAll("([^ \\t\\n])[\\t ]+([^ \\t])", "$1 $2"); - } - - return compiledString; - } - - @NotNull - private static String compile(@NotNull final JarClass klazz) { - String packageStatement = compilePackageStatement(klazz); - String classBody = compileClass(klazz); - return String.format("%s\n%s", packageStatement, classBody); - } - - private static String compileClass(@NotNull final JarClass klazz) { - return compileClass(klazz, false, null); - } - - @NotNull - private static String compileClass(@NotNull final JarClass klazz, boolean isEnumConstant, String enumName) { - final String methods = compileMethods(klazz, isEnumConstant); - final String fields = compileFields(klazz); - final String klazzHeader; - final String cotrs; - final String innerClasses; - if (isEnumConstant) { - cotrs = ""; - innerClasses = ""; - klazzHeader = enumName + " "; - } else { - cotrs = compileCotr(klazz); - innerClasses = compileInnerClasses(klazz); - klazzHeader = compileHeader(klazz); - } - - // Enums need to be handled quite a bit differently, but we also need to check if we are working on - // an enum constant to prevent infinite recursion - if (klazz.isEnum() && !isEnumConstant) { - String enumMembers = EMPTY_STRING; - Enum[] invokedExpression = getEnumConstants((JarClass) klazz); - - if (invokedExpression != null) { - enumMembers = Arrays.stream(invokedExpression) - .map(member -> compileClass(JarClass.forClass(member.getClass()), true, member.name())) - .collect(Collectors.joining("," + System.lineSeparator())); - } - - return String.format("%s{\n%s\n;%s\n%s\n%s\n}", klazzHeader, enumMembers, fields, methods, innerClasses); - } - - return String.format("%s{\n%s\n%s\n%s\n%s\n}", klazzHeader, fields, cotrs, methods, innerClasses); - } - - @Nullable - private static T[] getEnumConstants(@NotNull JarClass klazz) { - return getEnumConstants(klazz.getKlazz()); - } - - @Nullable - private static T[] getEnumConstants(@NotNull Class klazz) { - T[] invokedExpression = null; - try { - Method values = klazz.getMethod("values"); - values.setAccessible(true); - invokedExpression = (T[]) values.invoke(null); - } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException | ExceptionInInitializerError | NoClassDefFoundError e) { - System.err.println(String.format("Failed to load enum \"%s\"", klazz.getName())); - } - - return invokedExpression; - } - - /** - * Produces a String containing a source code version of the package name declaration - * - * @param klazz the {@link JarClass} to create the declaration for - * @return source code version of the package name declaration - */ - @NotNull - private static String compilePackageStatement(@NotNull final JarClass klazz) { - return String.format("package %s;\n", klazz.packageName()); - } - - @NotNull - private static String compileHeader(@NotNull final JarClass klazz) { - final boolean enumTypeClass = klazz.isEnum(); - final String annotationS; - if (klazz.isAnnotation() && klazz.getKlazz().isAnnotationPresent(Retention.class)) { - RetentionPolicy rentionPolicy = klazz.getKlazz().getAnnotation(Retention.class).value(); - annotationS = "@" + JarClass.safeFullNameForClass(Retention.class) + "(" + JarClass.safeFullNameForClass(RetentionPolicy.class) + "." + rentionPolicy.name() + ") "; - } else { - annotationS = ""; - } - final String security = klazz.security().getModifier() + (klazz.security() == SecurityModifier.PACKAGE ? EMPTY_STRING : " "); - final String finalS = klazz.isFinal() && !enumTypeClass ? "final " : EMPTY_STRING; - final String staticS = klazz.isStatic() && !enumTypeClass ? "static " : EMPTY_STRING; - final String abstractS = klazz.isAbstract() && !enumTypeClass && !klazz.isAnnotation() ? "abstract " : EMPTY_STRING; - - final String typeS; - if (klazz.isAnnotation()) { - typeS = "@interface "; - } else if (klazz.isInterface()) { - typeS = "interface "; - } else if (enumTypeClass) { - typeS = "enum "; - } else { - typeS = "class "; - } - - final String genericS; - TypeVariable>[] typeParameters = klazz.getKlazz().getTypeParameters(); - genericS = convertTypeParametersToString(typeParameters); - - final String nameS = klazz.name(); - final String extendsS; - Class extendsClazz = klazz.extendsClass(); - if (extendsClazz != null && !(extendsClazz.equals(Enum.class))) { - extendsS = "extends " + JarType.toString(klazz.extendsGenericClass()) + " "; - } else { - extendsS = EMPTY_STRING; - } - - StringBuilder implementsS = new StringBuilder(); - if (klazz.implementsInterfaces().length > 0 && !(klazz.isAnnotation() && klazz.implementsInterfaces().length == 1)) { - implementsS = klazz.isInterface() ? new StringBuilder("extends ") : new StringBuilder("implements "); - implementsS.append(Utils.arrayToCommaSeparatedList(klazz.implementsGenericInterfaces(), x -> { - if (x.equals(Annotation.class)) return null; - - return JarType.toString(x); - })); - implementsS.append(" "); - } - - return String.format("%s%s%s%s%s%s%s%s%s%s", annotationS, security, staticS, abstractS, finalS, typeS, nameS, genericS, extendsS, implementsS); - } - - @NotNull - private static String compileCotr(@NotNull final JarClass klazz) { - // Interfaces don't have constructors - if (klazz.isInterface()) return EMPTY_STRING; - - final Set cotrs = klazz.constructors(); - StringBuilder compiledCotr = new StringBuilder(); - for (CompileableString cotr : cotrs) { - compiledCotr.append(cotr.compileToString()); - } - - return String.join("\n", Arrays.stream(compiledCotr.toString().split("\n")).map(x -> INDENT + x).toArray(String[]::new)); - } - - /** - * Returns a String contains the default value for a given type - * - * @param type a {@link Type} to get the default value for - * @return a String with the default type for the parameter type - */ - public static String defaultValueForType(Type type) { - return defaultValueForType(type, false); - } - - - /** - * Returns a String contains the default value for a given type - * - * @param type a {@link Type} to get the default value for - * @param constant {@code true} if the returned value should be a constant - * @return a String with the default type for the parameter type - */ - public static String defaultValueForType(Type type, boolean constant) { - if (!(type instanceof Class)) { - if (type instanceof ParameterizedType) return defaultValueForType(((ParameterizedType) type).getRawType()); - return defaultValueForType(Object.class); - } - - if (type.equals(int.class) || type.equals(Integer.class)) { - return "0"; - } else if (type.equals(double.class) || type.equals(Double.class)) { - return "0.0"; - } else if (type.equals(long.class) || type.equals(Long.class)) { - return "0L"; - } else if (type.equals(byte.class) || type.equals(Byte.class)) { - return "(byte) 0"; - } else if (type.equals(short.class) || type.equals(Short.class)) { - return "(short) 0"; - } else if (type.equals(boolean.class) || type.equals(Boolean.class)) { - return "false"; - } else if (type.equals(float.class) || type.equals(Float.class)) { - return "(float) 0"; - } else if (type.equals(char.class) || type.equals(Character.class)) { - return "(char) 0"; - } else if (type.equals(String.class)) { - return "\"\""; - } else if (((Class) type).isArray()) { - if (constant) - return "{}"; - else - return String.format("new %s {}", JarType.toString(type)); - } else if (((Class) type).isEnum()) { - Enum[] enumConstants = getEnumConstants((Class) type); - if (enumConstants == null) { - if (constant) throw new RuntimeException("Cannot determine constant value!"); - - return "null"; - } - - return JarType.toString(type) + "." + enumConstants[0].name(); - } else { - return "null"; - } - } - - @NotNull - private static String compileMethods(@NotNull final JarClass klazz, boolean isEnumField) { - final Set methods = klazz.methods(); - StringBuilder compiledMethods = new StringBuilder(); - for (JarMethod method : methods) { - // Skip create methods for these types of things, enum fields can't have static methods - if ((isEnumField || klazz.isInterface()) && method.isStatic()) continue; - // Check if the enum method we are about to write could actually exist - if (isEnumField) { - // todo: fix this issue since Enum members may have static classes - if (method.isFinal()) - continue; - Class declaringClass = klazz.getKlazz().getDeclaringClass(); - if (declaringClass != null) { - try { - declaringClass.getDeclaredMethod(method.name(), method.parameterTypes()); - continue; - } catch (NoSuchMethodException ignored) { } - } - } - - // Figure method signature - final String security; - if (klazz.isInterface()) - security = EMPTY_STRING; - else - security = method.security().getModifier() + (method.security() == SecurityModifier.PACKAGE ? EMPTY_STRING : " "); - final String finalS = method.isFinal() ? "final " : EMPTY_STRING; - final String staticS = method.isStatic() ? "static " : EMPTY_STRING; - final String abstractS; - if (klazz.isInterface()) - abstractS = EMPTY_STRING; - else - abstractS = method.isAbstract() ? "abstract " : EMPTY_STRING; - final String returnTypeS = JarType.toString(method.genericReturnType()); - final String nameS = method.name(); - final String parametersS = Utils.arrayToCommaSeparatedList(method.parameters(), x -> x); - final String throwsS = method.requiresThrowsSignature() ? " throws " + Utils.arrayToCommaSeparatedList(method.throwsTypes(), JarType::toString) : ""; - final String genericS; - TypeVariable[] typeParameters = method.typeParameters(); - genericS = convertTypeParametersToString(typeParameters); - - // What should the method body be? - final String stubMethod; - final Type returnType = method.genericReturnType(); - if (returnType.equals(void.class)) { - stubMethod = EMPTY_STRING; - } else { - stubMethod = INDENT + "return " + JarConstructor.castedDefaultType(returnType, klazz) + ";"; - } - - // Finally, put all of the pieces together - compiledMethods.append('\n').append(security).append(finalS).append(staticS).append(abstractS).append(genericS).append(returnTypeS).append(" ") - .append(nameS).append('(').append(parametersS).append(')').append(throwsS); - if (klazz.isAnnotation() && method.hasDefaultValue()) { - compiledMethods.append(" default ").append(defaultValueForType(method.defaultValue().getClass(), true)).append(";"); - } else if (method.isAbstract() || (klazz.isInterface() && !method.isStatic())) { - compiledMethods.append(";"); - } else { - compiledMethods.append(" {\n").append(stubMethod).append("\n}\n\n"); - } - - } - - if (compiledMethods.length() > 2 && compiledMethods.lastIndexOf("\n") == compiledMethods.length() - 1) { - compiledMethods.replace(compiledMethods.length() - 2, compiledMethods.length(), EMPTY_STRING); - } - - // Indent all of the methods we created - return Arrays.stream(compiledMethods.toString().split("\n")) - .collect(Collectors.joining(System.lineSeparator() + INDENT)); - } - - private static String convertTypeParametersToString(TypeVariable[] typeParameters) { - String genericS; - if (typeParameters.length == 0) { - genericS = " "; - } else { - String typeParams = Utils.arrayToCommaSeparatedList(typeParameters, typeParam -> { - if (typeParam.getBounds()[0] == Object.class) { - return typeParam.getName(); - } else { - return typeParam.getName() + " extends " + JarType.toString(typeParam.getBounds()[0]); - } - }); - genericS = "<" + typeParams + "> "; - } - - return genericS; - } - - @NotNull - private static String compileFields(@NotNull final JarClass klazz) { - Set fields = klazz.fields(); - StringBuilder builder = new StringBuilder(); - for (JarField field : fields) { - Class superClazz = field.getClazz().extendsClass(); - - if (((field.getClazz().isEnum() || field.getClazz().isInnerClass()) && field.isStatic()) || field.isSynthetic()) continue; - if (superClazz != null) { - try { - superClazz.getDeclaredField(field.name()); - continue; - } catch (NoSuchFieldException ignored) { - - } - } - - builder.append(INDENT).append(field.compileToString()).append("\n"); - } - - return builder.toString(); - } - - @NotNull - private static String compileInnerClasses(@NotNull final JarClass klazz) { - Set> innerClasses = klazz.innerClasses(); - if (innerClasses.size() == 0) return EMPTY_STRING; - return innerClasses.stream() - .map(x -> ("\n" + compileClass(x) + "\n").split("\n")) - .flatMap(Arrays::stream) - .collect(Collectors.joining(System.lineSeparator() + INDENT)); - } - - public void write() { - writeDataWithDedicatedThread(compile()); - } -} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..a5c7930 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + + + %-5level %logger{15} - %msg%n + + + + + + + \ No newline at end of file