diff --git a/pom.xml b/pom.xml index 21c638f..b84c325 100644 --- a/pom.xml +++ b/pom.xml @@ -1,126 +1,110 @@ - + - 4.0.0 - com.mysema.codegen - codegen - 0.6.9.BUILD-SNAPSHOT - Codegen - Code generation and compilation for Java - - - com.mysema.home - mysema-source - 0.3.1 - - - jar + 4.0.0 + com.mysema.codegen + codegen + 0.6.9.BUILD-SNAPSHOT + Codegen + Code generation and compilation for Java - 2010 - - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - - - - scm:git:git@github.com:querydsl/codegen.git - git@github.com:querydsl/codegen.git - - - - 4.01 - 3.0.1 - 11.0.2 - 4.3.1 - - - - - com.google.guava - guava - 18.0 - - - - org.eclipse.jdt.core.compiler - ecj - ${ecj.version} - - - - - junit - junit - 4.8.1 - test - - - javax.servlet - servlet-api - - - - - javax.validation - validation-api - 1.0.CR3 - test - - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - - true - - - - com.springsource.bundlor - com.springsource.bundlor.maven - 1.0.0.RELEASE - - - bundlor - - bundlor - - - - - true - - - - - - - - - - com.springsource.repository.bundles.release - http://repository.springsource.com/maven/bundles/release - - + + com.mysema.home + mysema-source + 0.3.1 + + + jar + + 2010 + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + scm:git:git@github.com:querydsl/codegen.git + git@github.com:querydsl/codegen.git + + + + 1.6 + 1.6 + 4.01 + 3.0.1 + 11.0.2 + 4.3.1 + + + + + com.google.guava + guava + 18.0 + + + + org.eclipse.jdt.core.compiler + ecj + ${ecj.version} + + + + + junit + junit + 4.12 + test + + + javax.validation + validation-api + 1.0.CR3 + test + + + pl.pragmatists + JUnitParams + 1.1.1 + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + com.springsource.bundlor + com.springsource.bundlor.maven + 1.0.0.RELEASE + + + bundlor + + bundlor + + + + + true + + + + + + + + com.springsource.repository.bundles.release + http://repository.springsource.com/maven/bundles/release + + diff --git a/src/main/java/com/mysema/codegen/CompiledJavaFileObject.java b/src/main/java/com/mysema/codegen/CompiledJavaFileObject.java new file mode 100644 index 0000000..ab22f73 --- /dev/null +++ b/src/main/java/com/mysema/codegen/CompiledJavaFileObject.java @@ -0,0 +1,128 @@ +/* + * Copyright 2018, The Querydsl Team (http://www.querydsl.com/team) + * + * 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 com.mysema.codegen; + +import java.net.URI; +import java.io.*; +import javax.tools.JavaFileObject; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.Modifier; + +/** + * CompiledJavaFileObject defines a Java class file object that can be in a folder or in a JAR. + * + * NOTE: This is a derivative work from an article of atamur. + * Original article can be found here: http://atamur.blogspot.com/2009/10/using-built-in-javacompiler-with-custom.html + * + * @author matteo-gallo-bb + */ +class CompiledJavaFileObject implements JavaFileObject { + + private final String binaryName; + + private final URI uri; + + private final String name; + + public CompiledJavaFileObject(String binaryName, URI uri) { + this.uri = uri; + this.binaryName = binaryName; + // for FS based URI the path is not null, for JAR URI the scheme specific part is not null + name = uri.getPath() == null ? uri.getSchemeSpecificPart() : uri.getPath(); + } + + @Override + public URI toUri() { + return uri; + } + + @Override + public InputStream openInputStream() throws IOException { + // easy way to handle any URI! + return uri.toURL().openStream(); + } + + @Override + public OutputStream openOutputStream() { + throw new UnsupportedOperationException(); + } + + @Override + public String getName() { + return name; + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) { + throw new UnsupportedOperationException(); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + throw new UnsupportedOperationException(); + } + + @Override + public Writer openWriter() { + throw new UnsupportedOperationException(); + } + + @Override + public long getLastModified() { + return 0; + } + + @Override + public boolean delete() { + throw new UnsupportedOperationException(); + } + + @Override + public Kind getKind() { + return Kind.CLASS; + } + + @Override + // copied from SimpleJavaFileObject + public boolean isNameCompatible(String simpleName, Kind kind) { + String baseName = simpleName + kind.extension; + return kind.equals(getKind()) + && (baseName.equals(getName()) + || getName().endsWith("/" + baseName)); + } + + @Override + public NestingKind getNestingKind() { + throw new UnsupportedOperationException(); + } + + @Override + public Modifier getAccessLevel() { + throw new UnsupportedOperationException(); + } + + public String binaryName() { + return binaryName; + } + + + @Override + public String toString() { + return "CustomJavaFileObject{" + + "uri=" + uri + + '}'; + } +} diff --git a/src/main/java/com/mysema/codegen/JavaClassesFinder.java b/src/main/java/com/mysema/codegen/JavaClassesFinder.java new file mode 100644 index 0000000..eb91b2b --- /dev/null +++ b/src/main/java/com/mysema/codegen/JavaClassesFinder.java @@ -0,0 +1,123 @@ +/* + * Copyright 2018, The Querydsl Team (http://www.querydsl.com/team) + * + * 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 com.mysema.codegen; + +import javax.tools.JavaFileObject; +import java.io.File; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; + +import static javax.tools.JavaFileObject.Kind.CLASS; + +/** + * JavaClassesFinder finds Java file classes using the specified Classloader + * + * NOTE: This is a derivative work from an article of atamur. + * Original article can be found here: http://atamur.blogspot.com/2009/10/using-built-in-javacompiler-with-custom.html + * + * @author matteo-gallo-bb + */ +class JavaClassesFinder { + + private ClassLoader classLoader; + + JavaClassesFinder(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + /** + * List all the Java file classes that are present in the specified package. + * + * @param packageName + * @return a list of Java file classes + * @throws IOException + */ + public List listAll(String packageName) throws IOException { + String javaPackageName = packageName.replaceAll("\\.", "/"); + + List result = new ArrayList(); + + Enumeration urlEnumeration = classLoader.getResources(javaPackageName); + while (urlEnumeration.hasMoreElements()) { + // one URL for each jar on the classpath that has the given package + URL packageFolderURL = urlEnumeration.nextElement(); + result.addAll(listUnder(packageName, packageFolderURL)); + } + + return result; + } + + private Collection listUnder(String packageName, URL packageFolderURL) { + File directory = new File(packageFolderURL.getFile()); + if (directory.isDirectory()) { + // browse local .class files - useful for local execution + return processDir(packageName, directory); + } else { + // browse a jar file + return processJar(packageFolderURL); + } + } + + private List processJar(URL packageFolderURL) { + List result = new ArrayList(); + try { + JarURLConnection jarConn = (JarURLConnection) packageFolderURL.openConnection(); + String jarUri = jarConn.getJarFileURL().toString(); + String rootEntryName = jarConn.getEntryName() != null ? jarConn.getEntryName() : ""; + int rootEnd = rootEntryName.length() + 1; + + Enumeration entryEnum = jarConn.getJarFile().entries(); + while (entryEnum.hasMoreElements()) { + JarEntry jarEntry = entryEnum.nextElement(); + String name = jarEntry.getName(); + if (name.startsWith(rootEntryName) && name.indexOf('/', rootEnd) == -1 && name.endsWith(CLASS.extension)) { + URI uri = URI.create(jarUri + "!/" + name); + String binaryName = name.replaceAll("/", "."); + binaryName = binaryName.replaceAll(CLASS.extension + "$", ""); + + result.add(new CompiledJavaFileObject(binaryName, uri)); + } + } + } catch (Exception e) { + throw new CodegenException("Wasn't able to open " + packageFolderURL + " as a jar file", e); + } + return result; + } + + private List processDir(String packageName, File directory) { + List result = new ArrayList(); + + File[] childFiles = directory.listFiles(); + for (File childFile : childFiles) { + // We only want the .class files + if (childFile.isFile() && childFile.getName().endsWith(CLASS.extension)) { + String binaryName = packageName + "." + childFile.getName(); + binaryName = binaryName.replaceAll(CLASS.extension + "$", ""); + + result.add(new CompiledJavaFileObject(binaryName, childFile.toURI())); + } + } + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/mysema/codegen/MemFileManager.java b/src/main/java/com/mysema/codegen/MemFileManager.java index a4ca488..c9f1bdc 100644 --- a/src/main/java/com/mysema/codegen/MemFileManager.java +++ b/src/main/java/com/mysema/codegen/MemFileManager.java @@ -1,10 +1,12 @@ /* - * Copyright 2010, Mysema Ltd - * + * Copyright 2018, The Querydsl Team (http://www.querydsl.com/team) + * * 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 + * + * 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. @@ -43,6 +45,8 @@ public class MemFileManager extends ForwardingJavaFileManager { private final String urlPrefix; + private final JavaClassesFinder finder; + public MemFileManager(ClassLoader parent, StandardJavaFileManager sjfm) { super(sjfm); ramFileSystem = new HashMap>(); @@ -51,6 +55,7 @@ public MemFileManager(ClassLoader parent, StandardJavaFileManager sjfm) { classLoaderContent); classLoader = new MemClassLoader(parent, ramFileSystem); urlPrefix = MemFileSystemRegistry.DEFAULT.getUrlPrefix(this); + finder = new JavaClassesFinder(classLoader); } @Override @@ -117,6 +122,8 @@ public String inferBinaryName(Location loc, JavaFileObject javaFileObject) { String result; if (loc == StandardLocation.CLASS_PATH && javaFileObject instanceof MemJavaFileObject) { result = javaFileObject.getName(); + } else if (javaFileObject instanceof CompiledJavaFileObject) { + result = ((CompiledJavaFileObject) javaFileObject).binaryName(); } else { result = super.inferBinaryName(loc, javaFileObject); } @@ -133,9 +140,27 @@ public Iterable list(Location location, String pkg, Set ki boolean recurse) throws IOException { List result = new ArrayList(); - for (JavaFileObject f : super.list(location, pkg, kinds, recurse)) { - result.add(f); + + if (location == StandardLocation.PLATFORM_CLASS_PATH) { + // let standard manager handle + return super.list(location, pkg, kinds, recurse); + } else if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) { + for (JavaFileObject f : super.list(location, pkg, kinds, recurse)) { + result.add(f); + } + // app specific classes are here + result.addAll(finder.listAll(pkg)); } + + // here we add to the results the classes that Codegen has generated and has put in memory + result.addAll(addInMemoryClasses(location, pkg, kinds, recurse)); + + return result; + } + + private List addInMemoryClasses(Location location, String pkg, Set kinds, boolean recurse) { + List result = new ArrayList(); + if (location == StandardLocation.CLASS_PATH) { location = StandardLocation.CLASS_OUTPUT; } @@ -145,23 +170,32 @@ public Iterable list(Location location, String pkg, Set ki if (ramFileSystem.containsKey(key)) { Map locatedFiles = ramFileSystem.get(key); for (Map.Entry entry : locatedFiles.entrySet()) { - String name = entry.getKey(); - String packageName = ""; - if (name.indexOf('.') > -1) { - packageName = name.substring(0, name.lastIndexOf('.')); - } - if (recurse ? packageName.startsWith(pkg) : packageName.equals(pkg)) { - JavaFileObject candidate = entry.getValue(); - if (kinds.contains(candidate.getKind())) { - result.add(candidate); - } + JavaFileObject processedFile = processLocatedFile(pkg, kinds, recurse, entry); + if (processedFile != null) { + result.add(processedFile); } } } } + return result; } + private JavaFileObject processLocatedFile(String pkg, Set kinds, boolean recurse, Map.Entry entry) { + String name = entry.getKey(); + String packageName = ""; + if (name.indexOf('.') > -1) { + packageName = name.substring(0, name.lastIndexOf('.')); + } + if (recurse ? packageName.startsWith(pkg) : packageName.equals(pkg)) { + JavaFileObject candidate = entry.getValue(); + if (kinds.contains(candidate.getKind())) { + return candidate; + } + } + return null; + } + private void register(LocationAndKind key, JavaFileObject javaFileObject) { if (!ramFileSystem.containsKey(key)) { ramFileSystem.put(key, new HashMap()); diff --git a/src/main/java/com/mysema/codegen/SimpleCompiler.java b/src/main/java/com/mysema/codegen/SimpleCompiler.java index ae207e4..c1ace6c 100644 --- a/src/main/java/com/mysema/codegen/SimpleCompiler.java +++ b/src/main/java/com/mysema/codegen/SimpleCompiler.java @@ -1,10 +1,12 @@ /* - * Copyright 2010, Mysema Ltd - * + * Copyright 2018, The Querydsl Team (http://www.querydsl.com/team) + * * 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 + * + * 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. @@ -14,9 +16,7 @@ package com.mysema.codegen; import java.io.*; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.URLDecoder; +import java.net.*; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -68,12 +68,16 @@ public static String getClassPath(URLClassLoader cl) { ClassLoader c = cl; while (c instanceof URLClassLoader) { for (URL url : ((URLClassLoader)c).getURLs()) { - String decodedPath = URLDecoder.decode(url.getPath(), "UTF-8"); - paths.add(new File(decodedPath).getAbsolutePath()); + if (url.openConnection() instanceof JarURLConnection) { + paths.add(url.toString()); + } else { + String decodedPath = URLDecoder.decode(url.getPath(), "UTF-8"); + paths.add(new File(decodedPath).getAbsolutePath()); + } } c = c.getParent(); } - } + } return pathJoiner.join(paths); } catch (UnsupportedEncodingException e) { throw new CodegenException(e); diff --git a/src/test/java/com/mysema/codegen/JavaClassesFinderTest.java b/src/test/java/com/mysema/codegen/JavaClassesFinderTest.java new file mode 100644 index 0000000..431e3cb --- /dev/null +++ b/src/test/java/com/mysema/codegen/JavaClassesFinderTest.java @@ -0,0 +1,161 @@ +/* + * Copyright 2018, The Querydsl Team (http://www.querydsl.com/team) + * + * 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 com.mysema.codegen; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +import javax.tools.JavaFileObject; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.List; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static javax.tools.JavaFileObject.Kind.CLASS; +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; + +/** + * JavaClassesFinder unit test + * + * @author matteo-gallo-bb + */ +@RunWith(JUnitParamsRunner.class) +public class JavaClassesFinderTest { + + private File tmpBaseDir; + private File bootJarFile; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Before + public void setUp() throws IOException { + tmpBaseDir = temporaryFolder.newFolder("javaClassesFinderTest"); + bootJarFile = createSampleJarFile(); + } + + @Parameters({"com.mysema.codegen.model, ClassType", ", ClassRoot"}) + @Test + public void listAll(String packageName, String className) throws IOException { + // given + String bootJarFilePath = bootJarFile.getAbsolutePath(); + JavaClassesFinder finder = new JavaClassesFinder(new ClassLoaderMock(bootJarFilePath)); + + // when + List javaFileObjectList = finder.listAll(packageName); + + // then + assertNotNull(javaFileObjectList); + assertEquals(1, javaFileObjectList.size()); + assertTrue(javaFileObjectList.get(0) instanceof CompiledJavaFileObject); + CompiledJavaFileObject javaFileObject = (CompiledJavaFileObject) javaFileObjectList.get(0); + assertEquals(getFullQualifiedClassName(packageName, className), javaFileObject.binaryName()); + assertEquals(bootJarFilePath + "!/" + fromPackageToFolder(packageName, className), javaFileObject.getName()); + } + + private File createSampleJarFile() throws IOException { + File classTypeFile = createAndWriteTmpClassFile("ClassType.class"); + File classRootFile = createAndWriteTmpClassFile("ClassRoot.class"); + + //create a boot jar file + File bootJar = new File(tmpBaseDir,"exampleBoot.jar"); + FileOutputStream outputStream = new FileOutputStream(bootJar); + ZipOutputStream sampleJar = new ZipOutputStream(outputStream); + + // adding ClassRoot file to boot jar file + ZipEntry zeClassRoot = new ZipEntry(classRootFile.getName()); + sampleJar.putNextEntry(zeClassRoot); + writeTmpClassFileInJar(classRootFile, sampleJar); + + // adding ClassType file and folders to boot jar file + sampleJar.putNextEntry(new ZipEntry("com/")); + sampleJar.putNextEntry(new ZipEntry("com/mysema/")); + sampleJar.putNextEntry(new ZipEntry("com/mysema/codegen/")); + sampleJar.putNextEntry(new ZipEntry("com/mysema/codegen/model/")); + ZipEntry zeClassType = new ZipEntry("com/mysema/codegen/model/" + classTypeFile.getName()); + sampleJar.putNextEntry(zeClassType); + writeTmpClassFileInJar(classTypeFile, sampleJar); + + sampleJar.close(); + + return bootJar; + } + + private File createAndWriteTmpClassFile(String fileName) throws IOException { + //create a temp class file + File classTypeFile = new File(tmpBaseDir, fileName); + + //write it + BufferedWriter bw = new BufferedWriter(new FileWriter(classTypeFile)); + bw.write("This is the temporary file content"); + bw.close(); + + return classTypeFile; + } + + private void writeTmpClassFileInJar(File classTypeFile, ZipOutputStream sampleJar) throws IOException { + // writing temp class file in given jar file + FileInputStream inputStream = new FileInputStream(classTypeFile); + byte[] buffer = new byte[1024]; + for (int len; (len = inputStream.read(buffer)) > 0;) { + sampleJar.write(buffer, 0, len); + } + inputStream.close(); + sampleJar.closeEntry(); + } + + private String fromPackageToFolder(String packageName, String className) { + if (packageName.equals("")) { + return className + CLASS.extension; + } + return packageName.replaceAll("\\.", "/") + "/" + className + CLASS.extension; + } + + private String getFullQualifiedClassName(String packageName, String className) { + return packageName.equals("") ? className : packageName + "." + className; + } + + private class ClassLoaderMock extends ClassLoader { + + private String libraryFilePath; + + private ClassLoaderMock(String libraryFilePath) { + this.libraryFilePath = libraryFilePath; + } + + @Override + public Enumeration getResources(String name) { + Vector urlVector = new Vector(); + try { + String path = name.equals("") ? name : name + "/"; + urlVector.add(new URL("jar:file:" + libraryFilePath + "!/" + path)); + } catch (MalformedURLException e) { + fail("File " + libraryFilePath + " not found!"); + } + return urlVector.elements(); + } + } +} \ No newline at end of file diff --git a/template.mf b/template.mf index adc2cf5..213aca3 100644 --- a/template.mf +++ b/template.mf @@ -5,5 +5,6 @@ Bundle-ManifestVersion: 2 Import-Template: javax.annotation.*;version="0", javax.tools.*;version="0", + javax.lang.*;version="0", org.eclipse.jdt.*;version="3.7.2", com.google.common.*;version="${guava.version}"