Java tutorial
/******************************************************************************* * Copyright (c) 2006, 2019 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.compiler.apt.util; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.Objects; import java.util.ResourceBundle; import java.util.ServiceLoader; import java.util.Set; import java.util.StringTokenizer; import java.util.zip.ZipException; import javax.lang.model.SourceVersion; import javax.tools.FileObject; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; import org.eclipse.jdt.internal.compiler.apt.util.JrtFileSystem.JrtFileObject; import org.eclipse.jdt.internal.compiler.apt.util.ModuleLocationHandler.LocationContainer; import org.eclipse.jdt.internal.compiler.apt.util.ModuleLocationHandler.LocationWrapper; import org.eclipse.jdt.internal.compiler.apt.util.ModuleLocationHandler.ModuleLocationWrapper; import org.eclipse.jdt.internal.compiler.batch.FileSystem; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; import org.eclipse.jdt.internal.compiler.batch.Main; import org.eclipse.jdt.internal.compiler.batch.Main.ResourceBundleFactory; import org.eclipse.jdt.internal.compiler.batch.ModuleFinder; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.env.AccessRestriction; import org.eclipse.jdt.internal.compiler.env.AccessRule; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.parser.Parser; import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; import org.eclipse.jdt.internal.compiler.util.Util; /** * Implementation of the Standard Java File Manager */ public class EclipseFileManager implements StandardJavaFileManager { private static final String NO_EXTENSION = "";//$NON-NLS-1$ static final int HAS_EXT_DIRS = 1; static final int HAS_BOOTCLASSPATH = 2; static final int HAS_ENDORSED_DIRS = 4; static final int HAS_PROCESSORPATH = 8; static final int HAS_PROC_MODULEPATH = 16; Map<File, Archive> archivesCache; Charset charset; Locale locale; ModuleLocationHandler locationHandler; final Map<Location, URLClassLoader> classloaders; int flags; boolean isOnJvm9; File jrtHome; JrtFileSystem jrtSystem; public ResourceBundle bundle; String releaseVersion; public EclipseFileManager(Locale locale, Charset charset) { this.locale = locale == null ? Locale.getDefault() : locale; this.charset = charset == null ? Charset.defaultCharset() : charset; this.locationHandler = new ModuleLocationHandler(); this.classloaders = new HashMap<>(); this.archivesCache = new HashMap<>(); this.isOnJvm9 = isRunningJvm9(); try { initialize(Util.getJavaHome()); } catch (IOException e) { e.printStackTrace(); // ignore } try { this.bundle = ResourceBundleFactory.getBundle(this.locale); } catch (MissingResourceException e) { System.out.println( "Missing resource : " + Main.bundleName.replace('.', '/') + ".properties for locale " + locale); //$NON-NLS-1$//$NON-NLS-2$ } } protected void initialize(File javahome) throws IOException { if (this.isOnJvm9) { this.jrtSystem = new JrtFileSystem(javahome); this.archivesCache.put(javahome, this.jrtSystem); this.jrtHome = javahome; this.locationHandler.newSystemLocation(StandardLocation.SYSTEM_MODULES, this.jrtSystem); } else { this.setLocation(StandardLocation.PLATFORM_CLASS_PATH, getDefaultBootclasspath()); } Iterable<? extends File> defaultClasspath = getDefaultClasspath(); this.setLocation(StandardLocation.CLASS_PATH, defaultClasspath); // No annotation module path by default this.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, defaultClasspath); } /* (non-Javadoc) * @see javax.tools.JavaFileManager#close() */ @Override public void close() throws IOException { this.locationHandler.close(); for (Archive archive : this.archivesCache.values()) { archive.close(); } this.archivesCache.clear(); for (URLClassLoader cl : this.classloaders.values()) { cl.close(); } this.classloaders.clear(); } private void collectAllMatchingFiles(Location location, File file, String normalizedPackageName, Set<Kind> kinds, boolean recurse, ArrayList<JavaFileObject> collector) { if (file.equals(this.jrtHome)) { if (location instanceof ModuleLocationWrapper) { List<JrtFileObject> list = this.jrtSystem.list((ModuleLocationWrapper) location, normalizedPackageName, kinds, recurse, this.charset); for (JrtFileObject fo : list) { Kind kind = getKind(getExtension(fo.entryName)); if (kinds.contains(kind)) { collector.add(fo); } } } } else if (isArchive(file)) { Archive archive = this.getArchive(file); if (archive == Archive.UNKNOWN_ARCHIVE) return; String key = normalizedPackageName; if (!normalizedPackageName.endsWith("/")) {//$NON-NLS-1$ key += '/'; } // we have an archive file if (recurse) { for (String packageName : archive.allPackages()) { if (packageName.startsWith(key)) { List<String[]> types = archive.getTypes(packageName); if (types != null) { for (String[] entry : types) { final Kind kind = getKind(getExtension(entry[0])); if (kinds.contains(kind)) { collector.add(archive.getArchiveFileObject(packageName + entry[0], entry[1], this.charset)); } } } } } } else { List<String[]> types = archive.getTypes(key); if (types != null) { for (String[] entry : types) { final Kind kind = getKind(getExtension(entry[0])); if (kinds.contains(kind)) { collector.add(archive.getArchiveFileObject(key + entry[0], entry[1], this.charset)); } } } } } else { // we must have a directory File currentFile = new File(file, normalizedPackageName); if (!currentFile.exists()) return; String path; try { path = currentFile.getCanonicalPath(); } catch (IOException e) { return; } if (File.separatorChar == '/') { if (!path.endsWith(normalizedPackageName)) return; } else if (!path.endsWith(normalizedPackageName.replace('/', File.separatorChar))) return; File[] files = currentFile.listFiles(); if (files != null) { // this was a directory for (File f : files) { if (f.isDirectory() && recurse) { collectAllMatchingFiles(location, file, normalizedPackageName + '/' + f.getName(), kinds, recurse, collector); } else { final Kind kind = getKind(f); if (kinds.contains(kind)) { collector.add(new EclipseFileObject(normalizedPackageName + f.getName(), f.toURI(), kind, this.charset)); } } } } } } private Iterable<? extends File> concatFiles(Iterable<? extends File> iterable, Iterable<? extends File> iterable2) { ArrayList<File> list = new ArrayList<>(); if (iterable2 == null) return iterable; for (Iterator<? extends File> iterator = iterable.iterator(); iterator.hasNext();) { list.add(iterator.next()); } for (Iterator<? extends File> iterator = iterable2.iterator(); iterator.hasNext();) { list.add(iterator.next()); } return list; } /* (non-Javadoc) * @see javax.tools.JavaFileManager#flush() */ @Override public void flush() throws IOException { for (Archive archive : this.archivesCache.values()) { archive.flush(); } } private Archive getArchive(File f) { // check the archive (jar/zip) cache Archive archive = this.archivesCache.get(f); if (archive == null) { archive = Archive.UNKNOWN_ARCHIVE; // create a new archive if (f.exists()) { try { archive = new Archive(f); } catch (ZipException e) { // ignore } catch (IOException e) { // ignore } if (archive != null) { this.archivesCache.put(f, archive); } } this.archivesCache.put(f, archive); } return archive; } /* (non-Javadoc) * @see javax.tools.JavaFileManager#getClassLoader(javax.tools.JavaFileManager.Location) */ @Override public ClassLoader getClassLoader(Location location) { validateNonModuleLocation(location); Iterable<? extends File> files = getLocation(location); if (files == null) { // location is unknown return null; } URLClassLoader cl = this.classloaders.get(location); if (cl == null) { ArrayList<URL> allURLs = new ArrayList<>(); for (File f : files) { try { allURLs.add(f.toURI().toURL()); } catch (MalformedURLException e) { // the url is malformed - this should not happen throw new RuntimeException(e); } } URL[] result = new URL[allURLs.size()]; cl = new URLClassLoader(allURLs.toArray(result), getClass().getClassLoader()); this.classloaders.put(location, cl); } return cl; } private Iterable<? extends File> getPathsFrom(String path) { ArrayList<FileSystem.Classpath> paths = new ArrayList<>(); ArrayList<File> files = new ArrayList<>(); try { this.processPathEntries(Main.DEFAULT_SIZE_CLASSPATH, paths, path, this.charset.name(), false, false); } catch (IllegalArgumentException e) { return null; } for (FileSystem.Classpath classpath : paths) { files.add(new File(classpath.getPath())); } return files; } Iterable<? extends File> getDefaultBootclasspath() { List<File> files = new ArrayList<>(); String javaversion = System.getProperty("java.version");//$NON-NLS-1$ if (javaversion.length() > 3) javaversion = javaversion.substring(0, 3); long jdkLevel = CompilerOptions.versionToJdkLevel(javaversion); if (jdkLevel < ClassFileConstants.JDK1_6) { // wrong jdk - 1.6 or above is required return null; } for (FileSystem.Classpath classpath : org.eclipse.jdt.internal.compiler.util.Util.collectFilesNames()) { files.add(new File(classpath.getPath())); } return files; } Iterable<? extends File> getDefaultClasspath() { // default classpath ArrayList<File> files = new ArrayList<>(); String classProp = System.getProperty("java.class.path"); //$NON-NLS-1$ if ((classProp == null) || (classProp.length() == 0)) { return null; } else { StringTokenizer tokenizer = new StringTokenizer(classProp, File.pathSeparator); String token; while (tokenizer.hasMoreTokens()) { token = tokenizer.nextToken(); File file = new File(token); if (file.exists()) { files.add(file); } } } return files; } private Iterable<? extends File> getEndorsedDirsFrom(String path) { ArrayList<FileSystem.Classpath> paths = new ArrayList<>(); ArrayList<File> files = new ArrayList<>(); try { this.processPathEntries(Main.DEFAULT_SIZE_CLASSPATH, paths, path, this.charset.name(), false, false); } catch (IllegalArgumentException e) { return null; } for (FileSystem.Classpath classpath : paths) { files.add(new File(classpath.getPath())); } return files; } private Iterable<? extends File> getExtdirsFrom(String path) { ArrayList<FileSystem.Classpath> paths = new ArrayList<>(); ArrayList<File> files = new ArrayList<>(); try { this.processPathEntries(Main.DEFAULT_SIZE_CLASSPATH, paths, path, this.charset.name(), false, false); } catch (IllegalArgumentException e) { return null; } for (FileSystem.Classpath classpath : paths) { files.add(new File(classpath.getPath())); } return files; } private String getExtension(File file) { String name = file.getName(); return getExtension(name); } private String getExtension(String name) { int index = name.lastIndexOf('.'); if (index == -1) { return EclipseFileManager.NO_EXTENSION; } return name.substring(index); } /* (non-Javadoc) * @see javax.tools.JavaFileManager#getFileForInput(javax.tools.JavaFileManager.Location, java.lang.String, java.lang.String) */ @Override public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { validateNonModuleLocation(location); Iterable<? extends File> files = getLocation(location); if (files == null) { throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$ } String normalizedFileName = normalizedFileName(packageName, relativeName); for (File file : files) { if (file.isDirectory()) { // handle directory File f = new File(file, normalizedFileName); if (f.exists()) { return new EclipseFileObject(packageName + File.separator + relativeName, f.toURI(), getKind(f), this.charset); } else { continue; // go to next entry in the location } } else if (isArchive(file)) { // handle archive file Archive archive = getArchive(file); if (archive != Archive.UNKNOWN_ARCHIVE) { if (archive.contains(normalizedFileName)) { return archive.getArchiveFileObject(normalizedFileName, null, this.charset); } } } } return null; } private String normalizedFileName(String packageName, String relativeName) { StringBuilder sb = new StringBuilder(); sb.append(normalized(packageName)); if (sb.length() > 0) { sb.append('/'); } sb.append(relativeName.replace('\\', '/')); return sb.toString(); } /* (non-Javadoc) * @see javax.tools.JavaFileManager#getFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, java.lang.String, javax.tools.FileObject) */ @Override public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException { validateOutputLocation(location); Iterable<? extends File> files = getLocation(location); if (files == null) { throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$ } final Iterator<? extends File> iterator = files.iterator(); if (iterator.hasNext()) { File file = iterator.next(); String normalizedFileName = normalized(packageName) + '/' + relativeName.replace('\\', '/'); File f = new File(file, normalizedFileName); return new EclipseFileObject(packageName + File.separator + relativeName, f.toURI(), getKind(f), this.charset); } else { throw new IllegalArgumentException("location is empty : " + location);//$NON-NLS-1$ } } /* (non-Javadoc) * @see javax.tools.JavaFileManager#getJavaFileForInput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind) */ @Override public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { validateNonModuleLocation(location); if (kind != Kind.CLASS && kind != Kind.SOURCE) { throw new IllegalArgumentException("Invalid kind : " + kind);//$NON-NLS-1$ } Iterable<? extends File> files = getLocation(location); if (files == null) { throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$ } String normalizedFileName = normalized(className); normalizedFileName += kind.extension; for (File file : files) { if (file.isDirectory()) { // handle directory File f = new File(file, normalizedFileName); if (f.exists()) { return new EclipseFileObject(className, f.toURI(), kind, this.charset); } else { continue; // go to next entry in the location } } else if (isArchive(file)) { // handle archive file Archive archive = getArchive(file); if (archive != Archive.UNKNOWN_ARCHIVE) { if (archive.contains(normalizedFileName)) { return archive.getArchiveFileObject(normalizedFileName, null, this.charset); } } } } return null; } /* (non-Javadoc) * @see javax.tools.JavaFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject) */ @Override public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { validateOutputLocation(location); if (kind != Kind.CLASS && kind != Kind.SOURCE) { throw new IllegalArgumentException("Invalid kind : " + kind);//$NON-NLS-1$ } Iterable<? extends File> files = getLocation(location); if (files == null) { if (!location.equals(StandardLocation.CLASS_OUTPUT) && !location.equals(StandardLocation.SOURCE_OUTPUT)) throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$ // we will use either the sibling or user.dir if (sibling != null) { String normalizedFileName = normalized(className); int index = normalizedFileName.lastIndexOf('/'); if (index != -1) { normalizedFileName = normalizedFileName.substring(index + 1); } normalizedFileName += kind.extension; URI uri = sibling.toUri(); URI uri2 = null; try { String path = uri.getPath(); index = path.lastIndexOf('/'); if (index != -1) { path = path.substring(0, index + 1); path += normalizedFileName; } uri2 = new URI(uri.getScheme(), uri.getHost(), path, uri.getFragment()); } catch (URISyntaxException e) { throw new IllegalArgumentException("invalid sibling", e);//$NON-NLS-1$ } return new EclipseFileObject(className, uri2, kind, this.charset); } else { String normalizedFileName = normalized(className); normalizedFileName += kind.extension; File f = new File(System.getProperty("user.dir"), normalizedFileName);//$NON-NLS-1$ return new EclipseFileObject(className, f.toURI(), kind, this.charset); } } final Iterator<? extends File> iterator = files.iterator(); if (iterator.hasNext()) { File file = iterator.next(); String normalizedFileName = normalized(className); normalizedFileName += kind.extension; File f = new File(file, normalizedFileName); return new EclipseFileObject(className, f.toURI(), kind, this.charset); } else { throw new IllegalArgumentException("location is empty : " + location);//$NON-NLS-1$ } } /* (non-Javadoc) * @see javax.tools.StandardJavaFileManager#getJavaFileObjects(java.io.File[]) */ @Override public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) { return getJavaFileObjectsFromFiles(Arrays.asList(files)); } /* (non-Javadoc) * @see javax.tools.StandardJavaFileManager#getJavaFileObjects(java.lang.String[]) */ @Override public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) { return getJavaFileObjectsFromStrings(Arrays.asList(names)); } /* (non-Javadoc) * @see javax.tools.StandardJavaFileManager#getJavaFileObjectsFromFiles(java.lang.Iterable) */ @Override public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files) { ArrayList<JavaFileObject> javaFileArrayList = new ArrayList<>(); for (File f : files) { if (f.isDirectory()) { throw new IllegalArgumentException("file : " + f.getAbsolutePath() + " is a directory"); //$NON-NLS-1$ //$NON-NLS-2$ } javaFileArrayList.add(new EclipseFileObject(f.getAbsolutePath(), f.toURI(), getKind(f), this.charset)); } return javaFileArrayList; } /* (non-Javadoc) * @see javax.tools.StandardJavaFileManager#getJavaFileObjectsFromStrings(java.lang.Iterable) */ @Override public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) { ArrayList<File> files = new ArrayList<>(); for (String name : names) { files.add(new File(name)); } return getJavaFileObjectsFromFiles(files); } public Kind getKind(File f) { return getKind(getExtension(f)); } private Kind getKind(String extension) { if (Kind.CLASS.extension.equals(extension)) { return Kind.CLASS; } else if (Kind.SOURCE.extension.equals(extension)) { return Kind.SOURCE; } else if (Kind.HTML.extension.equals(extension)) { return Kind.HTML; } return Kind.OTHER; } /* (non-Javadoc) * @see javax.tools.StandardJavaFileManager#getLocation(javax.tools.JavaFileManager.Location) */ @Override public Iterable<? extends File> getLocation(Location location) { if (location instanceof LocationWrapper) { return getFiles(((LocationWrapper) location).paths); } LocationWrapper loc = this.locationHandler.getLocation(location, ""); //$NON-NLS-1$ if (loc == null) { return null; } return getFiles(loc.getPaths()); } private Iterable<? extends File> getOutputDir(String string) { if ("none".equals(string)) {//$NON-NLS-1$ return null; } File file = new File(string); if (file.exists() && !file.isDirectory()) { throw new IllegalArgumentException("file : " + file.getAbsolutePath() + " is not a directory");//$NON-NLS-1$//$NON-NLS-2$ } ArrayList<File> list = new ArrayList<>(1); list.add(file); return list; } /* (non-Javadoc) * @see javax.tools.JavaFileManager#handleOption(java.lang.String, java.util.Iterator) */ @Override public boolean handleOption(String current, Iterator<String> remaining) { try { switch (current) { case "-bootclasspath": //$NON-NLS-1$ if (remaining.hasNext()) { final Iterable<? extends File> bootclasspaths = getPathsFrom(remaining.next()); if (bootclasspaths != null) { Iterable<? extends File> iterable = getLocation(StandardLocation.PLATFORM_CLASS_PATH); if ((this.flags & EclipseFileManager.HAS_ENDORSED_DIRS) == 0 && (this.flags & EclipseFileManager.HAS_EXT_DIRS) == 0) { // override default bootclasspath setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootclasspaths); } else if ((this.flags & EclipseFileManager.HAS_ENDORSED_DIRS) != 0) { // endorseddirs have been processed first setLocation(StandardLocation.PLATFORM_CLASS_PATH, concatFiles(iterable, bootclasspaths)); } else { // extdirs have been processed first setLocation(StandardLocation.PLATFORM_CLASS_PATH, prependFiles(iterable, bootclasspaths)); } } this.flags |= EclipseFileManager.HAS_BOOTCLASSPATH; return true; } else { throw new IllegalArgumentException(); } case "--system": //$NON-NLS-1$ if (remaining.hasNext()) { final Iterable<? extends File> classpaths = getPathsFrom(remaining.next()); if (classpaths != null) { Iterable<? extends File> iterable = getLocation(StandardLocation.SYSTEM_MODULES); if (iterable != null) { setLocation(StandardLocation.SYSTEM_MODULES, concatFiles(iterable, classpaths)); } else { setLocation(StandardLocation.SYSTEM_MODULES, classpaths); } } return true; } else { throw new IllegalArgumentException(); } case "--upgrade-module-path": //$NON-NLS-1$ if (remaining.hasNext()) { final Iterable<? extends File> classpaths = getPathsFrom(remaining.next()); if (classpaths != null) { Iterable<? extends File> iterable = getLocation(StandardLocation.UPGRADE_MODULE_PATH); if (iterable != null) { setLocation(StandardLocation.UPGRADE_MODULE_PATH, concatFiles(iterable, classpaths)); } else { setLocation(StandardLocation.UPGRADE_MODULE_PATH, classpaths); } } return true; } else { throw new IllegalArgumentException(); } case "-classpath": //$NON-NLS-1$ case "-cp": //$NON-NLS-1$ if (remaining.hasNext()) { final Iterable<? extends File> classpaths = getPathsFrom(remaining.next()); if (classpaths != null) { Iterable<? extends File> iterable = getLocation(StandardLocation.CLASS_PATH); if (iterable != null) { setLocation(StandardLocation.CLASS_PATH, concatFiles(iterable, classpaths)); } else { setLocation(StandardLocation.CLASS_PATH, classpaths); } if ((this.flags & EclipseFileManager.HAS_PROCESSORPATH) == 0) { setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, classpaths); } else if ((this.flags & EclipseFileManager.HAS_PROC_MODULEPATH) == 0) { if (this.isOnJvm9) setLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, classpaths); } } return true; } else { throw new IllegalArgumentException(); } case "--module-path": //$NON-NLS-1$ case "-p": //$NON-NLS-1$ final Iterable<? extends File> classpaths = getPathsFrom(remaining.next()); if (classpaths != null) { Iterable<? extends File> iterable = getLocation(StandardLocation.MODULE_PATH); if (iterable != null) { setLocation(StandardLocation.MODULE_PATH, concatFiles(iterable, classpaths)); } else { setLocation(StandardLocation.MODULE_PATH, classpaths); } if ((this.flags & EclipseFileManager.HAS_PROCESSORPATH) == 0) { setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, classpaths); } else if ((this.flags & EclipseFileManager.HAS_PROC_MODULEPATH) == 0) { if (this.isOnJvm9) setLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, classpaths); } } return true; case "-encoding": //$NON-NLS-1$ if (remaining.hasNext()) { this.charset = Charset.forName(remaining.next()); return true; } else { throw new IllegalArgumentException(); } case "-sourcepath": //$NON-NLS-1$ if (remaining.hasNext()) { final Iterable<? extends File> sourcepaths = getPathsFrom(remaining.next()); if (sourcepaths != null) setLocation(StandardLocation.SOURCE_PATH, sourcepaths); return true; } else { throw new IllegalArgumentException(); } case "--module-source-path": //$NON-NLS-1$ if (remaining.hasNext()) { final Iterable<? extends File> sourcepaths = getPathsFrom(remaining.next()); if (sourcepaths != null && this.isOnJvm9) setLocation(StandardLocation.MODULE_SOURCE_PATH, sourcepaths); return true; } else { throw new IllegalArgumentException(); } case "-extdirs": //$NON-NLS-1$ if (this.isOnJvm9) { throw new IllegalArgumentException(); } if (remaining.hasNext()) { Iterable<? extends File> iterable = getLocation(StandardLocation.PLATFORM_CLASS_PATH); setLocation(StandardLocation.PLATFORM_CLASS_PATH, concatFiles(iterable, getExtdirsFrom(remaining.next()))); this.flags |= EclipseFileManager.HAS_EXT_DIRS; return true; } else { throw new IllegalArgumentException(); } case "-endorseddirs": //$NON-NLS-1$ if (remaining.hasNext()) { Iterable<? extends File> iterable = getLocation(StandardLocation.PLATFORM_CLASS_PATH); setLocation(StandardLocation.PLATFORM_CLASS_PATH, prependFiles(iterable, getEndorsedDirsFrom(remaining.next()))); this.flags |= EclipseFileManager.HAS_ENDORSED_DIRS; return true; } else { throw new IllegalArgumentException(); } case "-d": //$NON-NLS-1$ if (remaining.hasNext()) { final Iterable<? extends File> outputDir = getOutputDir(remaining.next()); if (outputDir != null) { setLocation(StandardLocation.CLASS_OUTPUT, outputDir); } return true; } else { throw new IllegalArgumentException(); } case "-s": //$NON-NLS-1$ if (remaining.hasNext()) { final Iterable<? extends File> outputDir = getOutputDir(remaining.next()); if (outputDir != null) { setLocation(StandardLocation.SOURCE_OUTPUT, outputDir); } return true; } else { throw new IllegalArgumentException(); } case "-processorpath": //$NON-NLS-1$ if (remaining.hasNext()) { final Iterable<? extends File> processorpaths = getPathsFrom(remaining.next()); if (processorpaths != null) { setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, processorpaths); } this.flags |= EclipseFileManager.HAS_PROCESSORPATH; return true; } else { throw new IllegalArgumentException(); } case "--processor-module-path": //$NON-NLS-1$ if (remaining.hasNext()) { final Iterable<? extends File> processorpaths = getPathsFrom(remaining.next()); if (processorpaths != null && this.isOnJvm9) { setLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, processorpaths); this.flags |= EclipseFileManager.HAS_PROC_MODULEPATH; } return true; } else { throw new IllegalArgumentException(); } case "--release": //$NON-NLS-1$ if (remaining.hasNext()) { this.releaseVersion = remaining.next(); return true; } else { throw new IllegalArgumentException(); } } } catch (IOException e) { // ignore } return false; } /* (non-Javadoc) * @see javax.tools.JavaFileManager#hasLocation(javax.tools.JavaFileManager.Location) */ @Override public boolean hasLocation(Location location) { try { return getLocationForModule(location, "") != null; //$NON-NLS-1$ } catch (IOException e) { // nothing to do } return false; } /* (non-Javadoc) * @see javax.tools.JavaFileManager#inferBinaryName(javax.tools.JavaFileManager.Location, javax.tools.JavaFileObject) */ @Override public String inferBinaryName(Location location, JavaFileObject file) { validateNonModuleLocation(location); String name = file.getName(); JavaFileObject javaFileObject = null; int index = name.lastIndexOf('.'); if (index != -1) { name = name.substring(0, index); } try { javaFileObject = getJavaFileForInput(location, name, file.getKind()); } catch (IOException e) { // ignore } catch (IllegalArgumentException iae) { return null; // Either unknown kind or location not present } if (javaFileObject == null) { return null; } return name.replace('/', '.'); } private boolean isArchive(File f) { String extension = getExtension(f); return extension.equalsIgnoreCase(".jar") || extension.equalsIgnoreCase(".zip");//$NON-NLS-1$//$NON-NLS-2$ } /* (non-Javadoc) * @see javax.tools.StandardJavaFileManager#isSameFile(javax.tools.FileObject, javax.tools.FileObject) */ @Override public boolean isSameFile(FileObject fileObject1, FileObject fileObject2) { // EclipseFileManager creates only EcliseFileObject if (!(fileObject1 instanceof EclipseFileObject)) throw new IllegalArgumentException("Unsupported file object class : " + fileObject1.getClass());//$NON-NLS-1$ if (!(fileObject2 instanceof EclipseFileObject)) throw new IllegalArgumentException("Unsupported file object class : " + fileObject2.getClass());//$NON-NLS-1$ return fileObject1.equals(fileObject2); } /* (non-Javadoc) * @see javax.tools.OptionChecker#isSupportedOption(java.lang.String) */ @Override public int isSupportedOption(String option) { return Options.processOptionsFileManager(option); } /* (non-Javadoc) * @see javax.tools.JavaFileManager#list(javax.tools.JavaFileManager.Location, java.lang.String, java.util.Set, boolean) */ @Override public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException { validateNonModuleLocation(location); Iterable<? extends File> allFilesInLocations = getLocation(location); if (allFilesInLocations == null) { throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$ } ArrayList<JavaFileObject> collector = new ArrayList<>(); String normalizedPackageName = normalized(packageName); for (File file : allFilesInLocations) { collectAllMatchingFiles(location, file, normalizedPackageName, kinds, recurse, collector); } return collector; } private String normalized(String className) { char[] classNameChars = className.toCharArray(); for (int i = 0, max = classNameChars.length; i < max; i++) { switch (classNameChars[i]) { case '\\': classNameChars[i] = '/'; break; case '.': classNameChars[i] = '/'; } } return new String(classNameChars); } private Iterable<? extends File> prependFiles(Iterable<? extends File> iterable, Iterable<? extends File> iterable2) { if (iterable2 == null) return iterable; ArrayList<File> list = new ArrayList<>(); for (Iterator<? extends File> iterator = iterable2.iterator(); iterator.hasNext();) { list.add(iterator.next()); } if (iterable != null) { for (Iterator<? extends File> iterator = iterable.iterator(); iterator.hasNext();) { list.add(iterator.next()); } } return list; } private boolean isRunningJvm9() { return (SourceVersion.latest().compareTo(SourceVersion.RELEASE_8) > 0); } /* (non-Javadoc) * @see javax.tools.StandardJavaFileManager#setLocation(javax.tools.JavaFileManager.Location, java.lang.Iterable) */ @Override public void setLocation(Location location, Iterable<? extends File> files) throws IOException { if (location.isOutputLocation()) { // output location int count = 0; for (Iterator<? extends File> iterator = files.iterator(); iterator.hasNext();) { iterator.next(); count++; } if (count != 1) { throw new IllegalArgumentException("output location can only have one path");//$NON-NLS-1$ } } this.locationHandler.setLocation(location, "", getPaths(files)); //$NON-NLS-1$ } public void setLocale(Locale locale) { this.locale = locale == null ? Locale.getDefault() : locale; try { this.bundle = ResourceBundleFactory.getBundle(this.locale); } catch (MissingResourceException e) { System.out.println( "Missing resource : " + Main.bundleName.replace('.', '/') + ".properties for locale " + locale); //$NON-NLS-1$//$NON-NLS-2$ throw e; } } @SuppressWarnings({ "unchecked", "rawtypes" }) public void processPathEntries(final int defaultSize, final ArrayList paths, final String currentPath, String customEncoding, boolean isSourceOnly, boolean rejectDestinationPathOnJars) { String currentClasspathName = null; String currentDestinationPath = null; ArrayList currentRuleSpecs = new ArrayList(defaultSize); StringTokenizer tokenizer = new StringTokenizer(currentPath, File.pathSeparator + "[]", true); //$NON-NLS-1$ ArrayList tokens = new ArrayList(); while (tokenizer.hasMoreTokens()) { tokens.add(tokenizer.nextToken()); } // state machine final int start = 0; final int readyToClose = 1; // 'path' 'path1[rule];path2' final int readyToCloseEndingWithRules = 2; // 'path[rule]' 'path1;path2[rule]' final int readyToCloseOrOtherEntry = 3; // 'path[rule];' 'path;' 'path1;path2;' final int rulesNeedAnotherRule = 4; // 'path[rule1;' final int rulesStart = 5; // 'path[' 'path1;path2[' final int rulesReadyToClose = 6; // 'path[rule' 'path[rule1;rule2' final int destinationPathReadyToClose = 7; // 'path[-d bin' final int readyToCloseEndingWithDestinationPath = 8; // 'path[-d bin]' 'path[rule][-d bin]' final int destinationPathStart = 9; // 'path[rule][' final int bracketOpened = 10; // '.*[.*' final int bracketClosed = 11; // '.*([.*])+' final int error = 99; int state = start; String token = null; int cursor = 0, tokensNb = tokens.size(), bracket = -1; while (cursor < tokensNb && state != error) { token = (String) tokens.get(cursor++); if (token.equals(File.pathSeparator)) { switch (state) { case start: case readyToCloseOrOtherEntry: case bracketOpened: break; case readyToClose: case readyToCloseEndingWithRules: case readyToCloseEndingWithDestinationPath: state = readyToCloseOrOtherEntry; addNewEntry(paths, currentClasspathName, currentRuleSpecs, customEncoding, currentDestinationPath, isSourceOnly, rejectDestinationPathOnJars); currentRuleSpecs.clear(); break; case rulesReadyToClose: state = rulesNeedAnotherRule; break; case destinationPathReadyToClose: throw new IllegalArgumentException(this.bind("configure.incorrectDestinationPathEntry", //$NON-NLS-1$ currentPath)); case bracketClosed: cursor = bracket + 1; state = rulesStart; break; default: state = error; } } else if (token.equals("[")) { //$NON-NLS-1$ switch (state) { case start: currentClasspathName = ""; //$NON-NLS-1$ //$FALL-THROUGH$ case readyToClose: bracket = cursor - 1; //$FALL-THROUGH$ case bracketClosed: state = bracketOpened; break; case readyToCloseEndingWithRules: state = destinationPathStart; break; case readyToCloseEndingWithDestinationPath: state = rulesStart; break; case bracketOpened: default: state = error; } } else if (token.equals("]")) { //$NON-NLS-1$ switch (state) { case rulesReadyToClose: state = readyToCloseEndingWithRules; break; case destinationPathReadyToClose: state = readyToCloseEndingWithDestinationPath; break; case bracketOpened: state = bracketClosed; break; case bracketClosed: default: state = error; } } else { // regular word switch (state) { case start: case readyToCloseOrOtherEntry: state = readyToClose; currentClasspathName = token; break; case rulesStart: if (token.startsWith("-d ")) { //$NON-NLS-1$ if (currentDestinationPath != null) { throw new IllegalArgumentException(this.bind("configure.duplicateDestinationPathEntry", //$NON-NLS-1$ currentPath)); } currentDestinationPath = token.substring(3).trim(); state = destinationPathReadyToClose; break; } // else we proceed with a rule //$FALL-THROUGH$ case rulesNeedAnotherRule: if (currentDestinationPath != null) { throw new IllegalArgumentException(this.bind("configure.accessRuleAfterDestinationPath", //$NON-NLS-1$ currentPath)); } state = rulesReadyToClose; currentRuleSpecs.add(token); break; case destinationPathStart: if (!token.startsWith("-d ")) { //$NON-NLS-1$ state = error; } else { currentDestinationPath = token.substring(3).trim(); state = destinationPathReadyToClose; } break; case bracketClosed: for (int i = bracket; i < cursor; i++) { currentClasspathName += (String) tokens.get(i); } state = readyToClose; break; case bracketOpened: break; default: state = error; } } if (state == bracketClosed && cursor == tokensNb) { cursor = bracket + 1; state = rulesStart; } } switch (state) { case readyToCloseOrOtherEntry: break; case readyToClose: case readyToCloseEndingWithRules: case readyToCloseEndingWithDestinationPath: addNewEntry(paths, currentClasspathName, currentRuleSpecs, customEncoding, currentDestinationPath, isSourceOnly, rejectDestinationPathOnJars); break; case bracketOpened: case bracketClosed: default: // we go on anyway } } @SuppressWarnings({ "rawtypes", "unchecked" }) protected void addNewEntry(ArrayList paths, String currentClasspathName, ArrayList currentRuleSpecs, String customEncoding, String destPath, boolean isSourceOnly, boolean rejectDestinationPathOnJars) { int rulesSpecsSize = currentRuleSpecs.size(); AccessRuleSet accessRuleSet = null; if (rulesSpecsSize != 0) { AccessRule[] accessRules = new AccessRule[currentRuleSpecs.size()]; boolean rulesOK = true; Iterator i = currentRuleSpecs.iterator(); int j = 0; while (i.hasNext()) { String ruleSpec = (String) i.next(); char key = ruleSpec.charAt(0); String pattern = ruleSpec.substring(1); if (pattern.length() > 0) { switch (key) { case '+': accessRules[j++] = new AccessRule(pattern.toCharArray(), 0); break; case '~': accessRules[j++] = new AccessRule(pattern.toCharArray(), IProblem.DiscouragedReference); break; case '-': accessRules[j++] = new AccessRule(pattern.toCharArray(), IProblem.ForbiddenReference); break; case '?': accessRules[j++] = new AccessRule(pattern.toCharArray(), IProblem.ForbiddenReference, true/*keep looking for accessible type*/); break; default: rulesOK = false; } } else { rulesOK = false; } } if (rulesOK) { accessRuleSet = new AccessRuleSet(accessRules, AccessRestriction.COMMAND_LINE, currentClasspathName); } else { return; } } if (Main.NONE.equals(destPath)) { destPath = Main.NONE; // keep == comparison valid } if (rejectDestinationPathOnJars && destPath != null && (currentClasspathName.endsWith(".jar") || //$NON-NLS-1$ currentClasspathName.endsWith(".zip"))) { //$NON-NLS-1$ throw new IllegalArgumentException(this.bind("configure.unexpectedDestinationPathEntryFile", //$NON-NLS-1$ currentClasspathName)); } FileSystem.Classpath currentClasspath = FileSystem.getClasspath(currentClasspathName, customEncoding, isSourceOnly, accessRuleSet, destPath, null, this.releaseVersion); if (currentClasspath != null) { paths.add(currentClasspath); } } /* * Lookup the message with the given ID in this catalog and bind its * substitution locations with the given string. */ private String bind(String id, String binding) { return bind(id, new String[] { binding }); } /* * Lookup the message with the given ID in this catalog and bind its * substitution locations with the given string values. */ private String bind(String id, String[] arguments) { if (id == null) return "No message available"; //$NON-NLS-1$ String message = null; try { message = this.bundle.getString(id); } catch (MissingResourceException e) { // If we got an exception looking for the message, fail gracefully by just returning // the id we were looking for. In most cases this is semi-informative so is not too bad. return "Missing message: " + id + " in: " + Main.bundleName; //$NON-NLS-2$ //$NON-NLS-1$ } return MessageFormat.format(message, (Object[]) arguments); } private Iterable<? extends File> getFiles(final Iterable<? extends Path> paths) { if (paths == null) return null; return () -> new Iterator<File>() { Iterator<? extends Path> original = paths.iterator(); @Override public boolean hasNext() { return this.original.hasNext(); } @Override public File next() { return this.original.next().toFile(); } }; } private Iterable<? extends Path> getPaths(final Iterable<? extends File> files) { if (files == null) return null; return () -> new Iterator<Path>() { Iterator<? extends File> original = files.iterator(); @Override public boolean hasNext() { return this.original.hasNext(); } @Override public Path next() { return this.original.next().toPath(); } }; } private void validateFileObject(FileObject file) { // FIXME: fill-up } private void validateModuleLocation(Location location, String modName) { Objects.requireNonNull(location); if (modName == null) { throw new IllegalArgumentException("module must not be null"); //$NON-NLS-1$ } if (this.isOnJvm9) { if (!location.isModuleOrientedLocation() && !location.isOutputLocation()) { throw new IllegalArgumentException("location is module related :" + location.getName()); //$NON-NLS-1$ } } } private void validateNonModuleLocation(Location location) { Objects.requireNonNull(location); if (this.isOnJvm9) { if (location.isModuleOrientedLocation() && location.isOutputLocation()) { throw new IllegalArgumentException("location is module related :" + location.getName()); //$NON-NLS-1$ } } } private void validateOutputLocation(Location location) { Objects.requireNonNull(location); if (!location.isOutputLocation()) { throw new IllegalArgumentException("location is not output location :" + location.getName()); //$NON-NLS-1$ } } @Override public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) { return getJavaFileObjectsFromPaths(Arrays.asList(paths)); } @Override public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(Iterable<? extends Path> paths) { return getJavaFileObjectsFromFiles(getFiles(paths)); } @Override public Iterable<? extends Path> getLocationAsPaths(Location location) { if (location instanceof LocationWrapper) { return ((LocationWrapper) location).paths; } LocationWrapper loc = this.locationHandler.getLocation(location); if (loc == null) { return null; } return loc.getPaths(); } @Override public void setLocationFromPaths(Location location, Collection<? extends Path> paths) throws IOException { setLocation(location, getFiles(paths)); if (location == StandardLocation.MODULE_PATH) { // FIXME: same for module source path? Map<String, String> options = new HashMap<>(); // FIXME: Find a way to get the options from the EclipseCompiler and pass it to the parser. // FIXME: need to be the latest and not hardcoded value options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_9); options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_9); options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_9); CompilerOptions compilerOptions = new CompilerOptions(options); ProblemReporter problemReporter = new ProblemReporter( DefaultErrorHandlingPolicies.proceedWithAllProblems(), compilerOptions, new DefaultProblemFactory()); for (Path path : paths) { List<Classpath> mp = ModuleFinder.findModules(path.toFile(), null, new Parser(problemReporter, true), null, true, this.releaseVersion); for (Classpath cp : mp) { Collection<String> moduleNames = cp.getModuleNames(null); for (String string : moduleNames) { Path p = Paths.get(cp.getPath()); setLocationForModule(StandardLocation.MODULE_PATH, string, Collections.singletonList(p)); } } } } } @Override public boolean contains(Location location, FileObject fo) throws IOException { validateFileObject(fo); Iterable<? extends File> files = getLocation(location); if (files == null) { throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$ } for (File file : files) { if (file.isDirectory()) { if (fo instanceof EclipseFileObject) { Path filepath = ((EclipseFileObject) fo).f.toPath(); if (filepath.startsWith(Paths.get(file.toURI()).toAbsolutePath())) { return true; } } } else if (isArchive(file)) { if (fo instanceof ArchiveFileObject) { Archive archive = getArchive(file); if (archive != Archive.UNKNOWN_ARCHIVE) { if (archive.contains(((ArchiveFileObject) fo).entryName)) { return true; } } } } } return false; } @Override public Location getLocationForModule(Location location, String moduleName) throws IOException { validateModuleLocation(location, moduleName); Location result = this.locationHandler.getLocation(location, moduleName); if (result == null && location == StandardLocation.CLASS_OUTPUT) { LocationWrapper wrapper = this.locationHandler.getLocation(StandardLocation.MODULE_SOURCE_PATH, moduleName); deriveOutputLocationForModules(moduleName, wrapper.paths); result = getLocationForModule(location, moduleName); } else if (result == null && location == StandardLocation.SOURCE_OUTPUT) { LocationWrapper wrapper = this.locationHandler.getLocation(StandardLocation.MODULE_SOURCE_PATH, moduleName); deriveSourceOutputLocationForModules(moduleName, wrapper.paths); result = getLocationForModule(location, moduleName); } return result; } @Override public Location getLocationForModule(Location location, JavaFileObject fo) { validateModuleLocation(location, ""); //$NON-NLS-1$ Path path = null; if (fo instanceof ArchiveFileObject) { path = ((ArchiveFileObject) fo).file.toPath(); return this.locationHandler.getLocation(location, path); } else if (fo instanceof EclipseFileObject) { path = ((EclipseFileObject) fo).f.toPath(); try { path = path.toRealPath(); } catch (IOException e) { e.printStackTrace(); } LocationContainer container = this.locationHandler.getLocation(location); while (path != null) { Location loc = container.get(path); if (loc != null) return loc; path = path.getParent(); } } return null; } @Override public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws IOException { // FIXME: Need special handling in case of module class loaders. return ServiceLoader.load(service, getClassLoader(location)); } @Override public String inferModuleName(Location location) throws IOException { if (location instanceof ModuleLocationWrapper) { ModuleLocationWrapper wrapper = (ModuleLocationWrapper) location; return wrapper.modName; } return null; } @Override public Iterable<Set<Location>> listLocationsForModules(Location location) { validateModuleLocation(location, ""); //$NON-NLS-1$ return this.locationHandler.listLocationsForModules(location); } @Override public Path asPath(FileObject file) { validateFileObject(file); EclipseFileObject eclFile = (EclipseFileObject) file; if (eclFile.f != null) { return eclFile.f.toPath(); } return null; } private void deriveOutputLocationForModules(String moduleName, Collection<? extends Path> paths) { LocationWrapper wrapper = this.locationHandler.getLocation(StandardLocation.CLASS_OUTPUT, moduleName); if (wrapper == null) { // First get from our internally known location for legacy/unnamed location wrapper = this.locationHandler.getLocation(StandardLocation.CLASS_OUTPUT, ""); //$NON-NLS-1$ if (wrapper == null) { wrapper = this.locationHandler.getLocation(StandardLocation.CLASS_OUTPUT); } if (wrapper != null) { Iterator<? extends Path> iterator = wrapper.paths.iterator(); if (iterator.hasNext()) { try { // Per module output location is always a singleton list Path path = iterator.next().resolve(moduleName); this.locationHandler.setLocation(StandardLocation.CLASS_OUTPUT, moduleName, Collections.singletonList(path)); } catch (Exception e) { e.printStackTrace(); } } } } } private void deriveSourceOutputLocationForModules(String moduleName, Collection<? extends Path> paths) { LocationWrapper wrapper = this.locationHandler.getLocation(StandardLocation.SOURCE_OUTPUT, moduleName); if (wrapper == null) { // First get from our internally known location for legacy/unnamed location wrapper = this.locationHandler.getLocation(StandardLocation.SOURCE_OUTPUT, ""); //$NON-NLS-1$ if (wrapper == null) { wrapper = this.locationHandler.getLocation(StandardLocation.SOURCE_OUTPUT); } if (wrapper != null) { Iterator<? extends Path> iterator = wrapper.paths.iterator(); if (iterator.hasNext()) { try { // Per module output location is always a singleton list Path path = iterator.next().resolve(moduleName); this.locationHandler.setLocation(StandardLocation.SOURCE_OUTPUT, moduleName, Collections.singletonList(path)); } catch (Exception e) { e.printStackTrace(); } } } } } @Override public void setLocationForModule(Location location, String moduleName, Collection<? extends Path> paths) throws IOException { validateModuleLocation(location, moduleName); this.locationHandler.setLocation(location, moduleName, paths); if (location == StandardLocation.MODULE_SOURCE_PATH) { LocationWrapper wrapper = this.locationHandler.getLocation(StandardLocation.CLASS_OUTPUT, moduleName); if (wrapper == null) { wrapper = this.locationHandler.getLocation(StandardLocation.CLASS_OUTPUT, ""); //$NON-NLS-1$ if (wrapper != null) { Iterator<? extends Path> iterator = wrapper.paths.iterator(); if (iterator.hasNext()) { // Per module output location is always a singleton list Path path = iterator.next().resolve(moduleName); this.locationHandler.setLocation(StandardLocation.CLASS_OUTPUT, moduleName, Collections.singletonList(path)); } } } } } }