diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/CloseableFilterableJavaFileObjectIterable.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/CloseableFilterableJavaFileObjectIterable.java index 5b0aaef9c..29833a728 100644 --- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/CloseableFilterableJavaFileObjectIterable.java +++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/CloseableFilterableJavaFileObjectIterable.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -102,4 +102,7 @@ public abstract class CloseableFilterableJavaFileObjectIterable implements Itera } abstract void close(); + + abstract void reset(); + } \ No newline at end of file diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/IterableClasspath.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/IterableClasspath.java index bc92d4f73..bfad28718 100644 --- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/IterableClasspath.java +++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/IterableClasspath.java @@ -97,7 +97,7 @@ public class IterableClasspath extends CloseableFilterableJavaFileObjectIterable class ClasspathEntriesIterator implements Iterator { private int currentClasspathEntriesIndex = 0; - // Either a directory or an archive will be open at any one time + // Walking one of three possible things: directory tree, zip, or Java runtime packaged in JDK9+ form private File openDirectory = null; private DirEnumeration openDirectoryEnumeration = null; @@ -105,19 +105,25 @@ public class IterableClasspath extends CloseableFilterableJavaFileObjectIterable private File openFile = null; private ZipEntry nestedZip = null; private Stack> openArchiveEnumeration = null; + + private File openJrt; + private JrtFsEnumeration openJrtEnumeration = null; private JavaFileObject nextEntry = null; private void findNext() { if (nextEntry == null) { try { - while (openArchive!=null || openDirectory!=null || currentClasspathEntriesIndex < classpathEntries.size()) { - if (openArchive == null && openDirectory == null) { + while (openArchive!=null || openDirectory!=null || openJrt != null || currentClasspathEntriesIndex < classpathEntries.size()) { + if (openArchive == null && openDirectory == null && openJrt == null) { // Open the next item File nextFile = classpathEntries.get(currentClasspathEntriesIndex); if (nextFile.isDirectory()) { openDirectory = nextFile; openDirectoryEnumeration = new DirEnumeration(nextFile); + } else if (nextFile.getName().endsWith("jrt-fs.jar")) { + openJrt = nextFile; + openJrtEnumeration = new JrtFsEnumeration(nextFile,null); } else { openFile = nextFile; openArchive = new ZipFile(nextFile); @@ -166,6 +172,17 @@ public class IterableClasspath extends CloseableFilterableJavaFileObjectIterable } openDirectoryEnumeration = null; openDirectory = null; + } else if (openJrtEnumeration != null) { + while (openJrtEnumeration.hasMoreElements()) { + JrtEntryJavaFileObject jrtEntry = openJrtEnumeration.nextElement(); + String name = openJrtEnumeration.getName(jrtEntry); + if (accept(name)) { + nextEntry = jrtEntry; + return; + } + } + openJrtEnumeration = null; + openJrt = null; } } } catch (IOException ioe) { diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/IterableJrtModule.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/IterableJrtModule.java new file mode 100644 index 000000000..49174fb50 --- /dev/null +++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/IterableJrtModule.java @@ -0,0 +1,111 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 org.springframework.cloud.function.compiler.java; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +import javax.tools.JavaFileObject; + +import org.springframework.cloud.function.compiler.java.MemoryBasedJavaFileManager.CompilationInfoCache; + +/** + * Iterable that will produce an iterator that returns classes found + * in a specific module tree within the Java runtime image that exists in + * Java 9 and later. + * + * @author Andy Clement + */ +public class IterableJrtModule extends CloseableFilterableJavaFileObjectIterable { + +// private static Logger logger = LoggerFactory.getLogger(IterableJrtModule.class); + + private Path moduleRootPath; + + Map walkers = new HashMap<>(); + + /** + * @param compilationInfoCache cache of info that may help accelerate compilation + * @param moduleRootPath path to the base of the relevant module within the JRT image + * @param packageNameFilter an optional package name if choosing to filter (e.g. com.example) + * @param includeSubpackages if true, include results in subpackages of the specified package filter + */ + public IterableJrtModule(CompilationInfoCache compilationInfoCache, Path moduleRootPath, String packageNameFilter, + boolean includeSubpackages) { + super(compilationInfoCache, packageNameFilter, includeSubpackages); + this.moduleRootPath = moduleRootPath; + } + + public Iterator iterator() { + JrtFsEnumeration jrtFsWalker = walkers.get(moduleRootPath.toString()); + if (jrtFsWalker == null) { + jrtFsWalker = new JrtFsEnumeration(null, moduleRootPath); + walkers.put(moduleRootPath.toString(), jrtFsWalker); + } + jrtFsWalker.reset(); + return new IteratorOverJrtFsEnumeration(jrtFsWalker); + } + + class IteratorOverJrtFsEnumeration implements Iterator { + + private JavaFileObject nextEntry = null; + + private JrtFsEnumeration jrtEnumeration; + + public IteratorOverJrtFsEnumeration(JrtFsEnumeration jrtFsWalker) { + this.jrtEnumeration = jrtFsWalker; + } + + private void findNext() { + if (nextEntry == null) { + while (jrtEnumeration.hasMoreElements()) { + JrtEntryJavaFileObject jrtEntry = jrtEnumeration.nextElement(); + String name = jrtEnumeration.getName(jrtEntry); + if (accept(name)) { + nextEntry = jrtEntry; + return; + } + } + } + } + + public boolean hasNext() { + findNext(); + return nextEntry != null; + } + + public JavaFileObject next() { + findNext(); + if (nextEntry == null) { + throw new NoSuchElementException(); + } + JavaFileObject retval = nextEntry; + nextEntry = null; + return retval; + } + } + + public void close() { + } + + public void reset() { + close(); + } +} \ No newline at end of file diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/JrtEntryJavaFileObject.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/JrtEntryJavaFileObject.java new file mode 100644 index 000000000..99514b54a --- /dev/null +++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/JrtEntryJavaFileObject.java @@ -0,0 +1,149 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 org.springframework.cloud.function.compiler.java; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.tools.JavaFileObject; + +/** + * A JavaFileObject that represents a class from the Java runtime as packaged in Java 9 and later. + * + * @author Andy Clement + */ +public class JrtEntryJavaFileObject implements JavaFileObject { + + private String pathToClassString; + private Path path; + + /** + * @param path entry in the Java runtime filesystem, for example '/modules/java.base/java/lang/Object.class' + */ + public JrtEntryJavaFileObject(Path path) { + this.pathToClassString = path.subpath(2, path.getNameCount()).toString(); // e.g. java/lang/Object.class + this.path = path; + } + + @Override + public URI toUri() { + return path.toUri(); + } + + /** + * @return the path of the file relative to the base directory, for example: a/b/c/D.class + */ + @Override + public String getName() { + return pathToClassString; + } + + @Override + public InputStream openInputStream() throws IOException { + byte[] bytes = Files.readAllBytes(path); + return new ByteArrayInputStream(bytes); + } + + @Override + public OutputStream openOutputStream() throws IOException { + throw new IllegalStateException("Only expected to be used for input"); + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) throws IOException { + // It is bytecode + throw new UnsupportedOperationException("openReader() not supported on class file: " + getName()); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + // It is bytecode + throw new UnsupportedOperationException("getCharContent() not supported on class file: " + getName()); + } + + @Override + public Writer openWriter() throws IOException { + throw new IllegalStateException("only expected to be used for input"); + } + + @Override + public long getLastModified() { + try { + return Files.getLastModifiedTime(path).toMillis(); + } catch (IOException ioe) { + throw new RuntimeException("Unable to determine last modified time of "+pathToClassString, ioe); + } + } + + @Override + public boolean delete() { + return false; // This object is for read only access to a class + } + + @Override + public Kind getKind() { + return Kind.CLASS; + } + + @Override + public boolean isNameCompatible(String simpleName, Kind kind) { + if (kind != Kind.CLASS) { + return false; + } + String name = getName(); + int lastSlash = name.lastIndexOf('/'); + return name.substring(lastSlash + 1).equals(simpleName + ".class"); + } + + @Override + public NestingKind getNestingKind() { + return null; + } + + @Override + public Modifier getAccessLevel() { + return null; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof JrtEntryJavaFileObject)) { + return false; + } + JrtEntryJavaFileObject that = (JrtEntryJavaFileObject)obj; + return (getName().equals(that.getName())); + } + + public String getPathToClassString() { + return pathToClassString; + } + +} \ No newline at end of file diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/JrtFsEnumeration.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/JrtFsEnumeration.java new file mode 100644 index 000000000..28dcf1634 --- /dev/null +++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/JrtFsEnumeration.java @@ -0,0 +1,128 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 org.springframework.cloud.function.compiler.java; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Walks a JrtFS treating it like a directory (to avoid overcomplicating the walking + * logic in IterableClasspath) + * + * @author Andy Clement + */ +public class JrtFsEnumeration implements Enumeration { + +// private final static Logger logger = LoggerFactory.getLogger(JrtFsEnumeration.class); + + private static URI JRT_URI = URI.create("jrt:/"); //$NON-NLS-1$ + + private final static FileSystem fs = FileSystems.getFileSystem(JRT_URI); + + private Path pathWithinJrt; + + private List jfos = new ArrayList<>(); + + private Integer counter = 0; + + private Boolean initialized = false; + + public JrtFsEnumeration(File jrtFsFile, Path pathWithinJrt) { + this.pathWithinJrt = pathWithinJrt; + ensureInitialized(); + } + + class FileCacheBuilderVisitor extends SimpleFileVisitor { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + int fnc = file.getNameCount(); + if (fnc >= 3 && file.toString().endsWith(".class")) { // There is a preceeding module name - e.g. /modules/java.base/java/lang/Object.class + // file.subpath(2, fnc); // e.g. java/lang/Object.class + jfos.add(new JrtEntryJavaFileObject(file)); + } + return FileVisitResult.CONTINUE; + } + } + + private void ensureInitialized() { + synchronized (initialized) { + if (initialized) { + return; + } + FileCacheBuilderVisitor visitor = new FileCacheBuilderVisitor(); + if (pathWithinJrt != null) { + try { + Files.walkFileTree(pathWithinJrt, visitor); + // System.out.println("JrtFs enumeration for '"+pathWithinJrt+"' with #"+jfos.size()+" entries"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + Iterable roots = fs.getRootDirectories(); + try { + for (java.nio.file.Path path : roots) { + Files.walkFileTree(path, visitor); + } + // System.out.println("JrtFs enumeration initialized with #"+jfos.size()+" entries"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + initialized = true; + } + } + + @Override + public boolean hasMoreElements() { + return counter < jfos.size(); + } + + @Override + public JrtEntryJavaFileObject nextElement() { + if (counter>=jfos.size()) { + throw new NoSuchElementException(); + } + JrtEntryJavaFileObject toReturn = jfos.get(counter++); + return toReturn; + } + + /** + * Return the relative path of this file to the base directory that the directory enumeration was + * started for. + * @param file a file discovered returned by this enumeration + * @return the relative path of the file (for example: a/b/c/D.class) + */ + public String getName(JrtEntryJavaFileObject file) { + return file.getPathToClassString(); + } + + public void reset() { + counter = 0; + } + +} diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/MemoryBasedJavaFileManager.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/MemoryBasedJavaFileManager.java index b3cc3e168..948f2bb81 100644 --- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/MemoryBasedJavaFileManager.java +++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/MemoryBasedJavaFileManager.java @@ -21,11 +21,19 @@ import java.io.IOException; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -76,7 +84,7 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { private CompilationInfoCache compilationInfoCache; - private Map iterables = new HashMap<>(); + private Map iterables = new HashMap<>(); public MemoryBasedJavaFileManager() { outputCollector = new CompilationOutputCollector(); @@ -159,6 +167,46 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { } } + private boolean packageCacheInitialized = false; + private Map packageCache = new HashMap(); + private ArchiveInfo moduleArchiveInfo; + + private class PackageCacheBuilderVisitor extends SimpleFileVisitor { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.getNameCount() > 3 && file.toString().endsWith(".class")) { + int fnc = file.getNameCount(); + if (fnc > 3) { // There is a package name - e.g. /modules/java.base/java/lang/Object.class + Path packagePath = file.subpath(2, fnc-1); // e.g. java/lang + String packagePathString = packagePath.toString()+"/"; + packageCache.put(packagePathString, file.subpath(0, fnc-1)); // java/lang -> /modules/java.base/java/lang + } + } + return FileVisitResult.CONTINUE; + } + } + + private synchronized ArchiveInfo buildPackageMap() { + if (!packageCacheInitialized) { + packageCacheInitialized = true; + Iterable roots = getJrtFs().getRootDirectories(); + PackageCacheBuilderVisitor visitor = new PackageCacheBuilderVisitor(); + try { + for (java.nio.file.Path path : roots) { + Files.walkFileTree(path, visitor); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + List ls = new ArrayList<>(); + ls.addAll(packageCache.keySet()); + Collections.sort(ls); + moduleArchiveInfo = new ArchiveInfo(ls, false); + } + return moduleArchiveInfo; + } + /** * Walk the specified archive and collect up the package names of any .class files encountered. If * the archive contains nested jars packaged in a BOOT style way (under a BOOT-INF/lib folder) then @@ -168,6 +216,10 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { * @return an ArchiveInfo encapsulating package info from the archive */ private ArchiveInfo buildArchiveInfo(File file) { + if (file.toString().endsWith("jrt-fs.jar")) { + // Special treatment for >=JDK9 - treat this as intention to use modules + return buildPackageMap(); + } List packageNames = new ArrayList<>(); boolean isBootJar = false; try (ZipFile openArchive = new ZipFile(file)) { @@ -234,12 +286,14 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { } static class Key { + private Location location; private String classpath; private String packageName; private Set kinds; private boolean recurse; - public Key(String classpath, String packageName, Set kinds, boolean recurse) { + public Key(Location location, String classpath, String packageName, Set kinds, boolean recurse) { + this.location = location; this.classpath = classpath; this.packageName = packageName; this.kinds = kinds; @@ -248,7 +302,7 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { @Override public int hashCode() { - return ((classpath.hashCode()*37+(packageName==null?0:packageName.hashCode()))*37+kinds.hashCode())*37+(recurse?1:0); + return (((location.hashCode()*37)+classpath.hashCode()*37+(packageName==null?0:packageName.hashCode()))*37+kinds.hashCode())*37+(recurse?1:0); } @Override @@ -257,7 +311,8 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { return false; } Key that = (Key)obj; - return classpath.equals(that.classpath) && + return location.equals(that.location) && + classpath.equals(that.classpath) && kinds.equals(that.kinds) && (recurse==that.recurse) && (packageName==null?(that.packageName==null):this.packageName.equals(that.packageName)); @@ -268,6 +323,9 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { if (platformClasspath == null) { platformClasspath = System.getProperty("sun.boot.class.path"); } + if (platformClasspath == null) { + platformClasspath = ""; + } return platformClasspath; } @@ -276,9 +334,20 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { Set kinds, boolean recurse) throws IOException { logger.debug("list({},{},{},{})", location, packageName, kinds, recurse); String classpath = ""; - if (location == StandardLocation.PLATFORM_CLASS_PATH + Path moduleRootPath = null; + if (location instanceof JDKModuleLocation && (kinds == null || kinds.contains(Kind.CLASS))) { + // list(org.springframework.cloud.function.compiler.java.MemoryBasedJavaFileManager$JDKModuleLocation@550a1967, + // java.lang,[SOURCE, CLASS, HTML, OTHER],false) + moduleRootPath = ((JDKModuleLocation)location).getModuleRootPath(); + logger.debug("For JDKModuleLocation "+location.toString()+" root path is "+moduleRootPath); + } else if (location == StandardLocation.PLATFORM_CLASS_PATH && (kinds == null || kinds.contains(Kind.CLASS))) { classpath = getPlatformClassPath(); +// if (classpath.length() == 0) { +// if (hasJrtFsPath()) { +// classpath = getJrtFsPath(); +// } +// } logger.debug("Creating iterable for boot class path: {}", classpath); } else if (location == StandardLocation.CLASS_PATH @@ -294,10 +363,14 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { classpath = javaClassPath; logger.debug("Creating iterable for class path: {}", classpath); } - Key k = new Key(classpath, packageName, kinds, recurse); - IterableClasspath resultIterable = iterables.get(k); + Key k = new Key(location, classpath, packageName, kinds, recurse); + CloseableFilterableJavaFileObjectIterable resultIterable = iterables.get(k); if (resultIterable == null) { - resultIterable = new IterableClasspath(compilationInfoCache, classpath, packageName, recurse); + if (moduleRootPath != null) { + resultIterable = new IterableJrtModule(compilationInfoCache, moduleRootPath, packageName, recurse); + } else { + resultIterable = new IterableClasspath(compilationInfoCache, classpath, packageName, recurse); + } iterables.put(k, resultIterable); } resultIterable.reset(); @@ -330,6 +403,9 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { if (cp == null) { cp = System.getProperty("java.class.path"); } + if (hasJrtFsPath()) { + cp = cp + File.pathSeparator + getJrtFsPath(); + } classpath = pathWithPlatformClassPathRemoved(cp); } return classpath; @@ -394,6 +470,10 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { logger.debug("getJavaFileForInput({},{},{})", location, className, kind); + // getJavaFileForInput(SOURCE_PATH,module-info,SOURCE) + if (className.equals("module-info")) { + return null; + } throw new IllegalStateException("Not expected to be used in this context"); } @@ -432,8 +512,8 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { @Override public void close() throws IOException { - Collection toClose = iterables.values(); - for (IterableClasspath icp: toClose) { + Collection toClose = iterables.values(); + for (CloseableFilterableJavaFileObjectIterable icp: toClose) { icp.close(); } } @@ -482,4 +562,138 @@ public class MemoryBasedJavaFileManager implements JavaFileManager { return resolvedAdditionalDependencies; } + private static URI JRT_URI = URI.create("jrt:/"); + + private static FileSystem fs; + + static class JDKModuleLocation implements Location { + private String moduleName; + private Path moduleRootPath; + + public JDKModuleLocation(String moduleName, Path moduleRootPath) { + this.moduleName = moduleName; + this.moduleRootPath = moduleRootPath; + } + + @Override + public String getName() { + return "MODULE"; + } + + @Override + public boolean isOutputLocation() { + return false; + } + + public String getModuleName() { + return moduleName; + } + + public Path getModuleRootPath() { + return moduleRootPath; + } + + public String toString() { + return "JDKModuleLocation(" + moduleName + ")"; + } + + public int hashCode() { + return moduleName.hashCode(); + } + + public boolean equals(Object other) { + if (!(other instanceof JDKModuleLocation)) { + return false; + } + return this.hashCode() == ((JDKModuleLocation)other).hashCode(); + } + + } + + public String inferModuleName(Location location) throws IOException { + if (location instanceof JDKModuleLocation) { + JDKModuleLocation m = (JDKModuleLocation)location; + return m.getModuleName(); + } + throw new IllegalStateException("Asked to inferModuleName from a "+location.getClass().getName()); + } + + static class ModuleIdentifierVisitor extends SimpleFileVisitor { + + private Map modules = new HashMap<>(); + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.getNameCount() > 2 && file.toString().endsWith(".class")) { + // /modules/jdk.rmic/sun/tools/tree/CaseStatement.class + String moduleName = file.getName(1).toString(); // jdk.rmic + Path moduleRootPath = file.subpath(0, 2); // /modules/jdk.rmic + if (!modules.containsKey(moduleName)) { + modules.put(moduleName, moduleRootPath); + } + } + return FileVisitResult.CONTINUE; + } + + public Set getModuleLocations() { + if (modules.size()==0) { + return Collections.emptySet(); + } else { + Set locations = new HashSet<>(); + for (Map.Entry moduleEntry: modules.entrySet()) { + locations.add(new JDKModuleLocation(moduleEntry.getKey(),moduleEntry.getValue())); + } + return locations; + } + } + } + + private String jrtFsFilePath = null; + + private boolean checkedForJrtFsPath = false; + + private boolean hasJrtFsPath() { + return getJrtFsPath() != null; + } + + private String getJrtFsPath() { + if (!checkedForJrtFsPath) { + String javaHome = System.getProperty("java.home"); + String jrtFsFilePath = javaHome + File.separator + "lib" + File.separator + "jrt-fs.jar"; + File jrtFsFile = new File(jrtFsFilePath); + if (jrtFsFile.exists()) { + this.jrtFsFilePath = jrtFsFilePath; + } + checkedForJrtFsPath = true; + } + return jrtFsFilePath; + } + + public Iterable> listLocationsForModules(Location location) throws IOException { + if (getJrtFsPath()!=null && location == StandardLocation.valueOf("SYSTEM_MODULES")) { + Set> ss = new HashSet<>(); + HashSet moduleLocations = new HashSet<>(); + ModuleIdentifierVisitor visitor = new ModuleIdentifierVisitor(); + Iterable roots = getJrtFs().getRootDirectories(); + try { + for (Path path: roots) { + Files.walkFileTree(path, visitor); + } + moduleLocations.addAll(visitor.getModuleLocations()); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + ss.add(moduleLocations); + return ss; + } else { + return Collections.emptySet(); + } + } + + private static FileSystem getJrtFs() { + if (fs == null) { + fs = FileSystems.getFileSystem(JRT_URI); + } + return fs; + } } diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/RuntimeJavaCompiler.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/RuntimeJavaCompiler.java index c10ec0ba4..dbcfc03ac 100644 --- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/RuntimeJavaCompiler.java +++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/java/RuntimeJavaCompiler.java @@ -61,7 +61,10 @@ public class RuntimeJavaCompiler { JavaFileObject sourceFile = InMemoryJavaFileObject.getSourceJavaFileObject(className, classSourceCode); Iterable compilationUnits = Arrays.asList(sourceFile); - CompilationTask task = compiler.getTask(null, fileManager , diagnosticCollector, null, null, compilationUnits); + List options = new ArrayList<>(); + options.add("-source"); + options.add("1.8"); + CompilationTask task = compiler.getTask(null, fileManager , diagnosticCollector, options, null, compilationUnits); boolean success = task.call(); CompilationResult compilationResult = new CompilationResult(success);