org.eclipse.jdt.internal.compiler.batch.FileSystem Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2000, 2024 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
* Stephan Herrmann - Contribution for
* Bug 440687 - [compiler][batch][null] improve command line option for external annotations
* Salesforce - Contribution for
* https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2529
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.batch;
import java.io.File;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.zip.ZipFile;
import javax.lang.model.SourceVersion;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator;
import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
import org.eclipse.jdt.internal.compiler.env.IModulePathEntry;
import org.eclipse.jdt.internal.compiler.env.IModule;
import org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.lookup.ModuleBinding;
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.env.IUpdatableModule;
import org.eclipse.jdt.internal.compiler.env.IUpdatableModule.UpdateKind;
import org.eclipse.jdt.internal.compiler.env.IUpdatableModule.UpdatesByKind;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.util.JRTUtil;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.compiler.util.Util;
public class FileSystem implements IModuleAwareNameEnvironment, SuffixConstants {
// Keep the type as ArrayList and not List as there are clients that are already written to expect ArrayList.
public static ArrayList EMPTY_CLASSPATH = new ArrayList<>();
/**
* A Classpath
, even though an IModuleLocation, can represent a plain
* classpath location too. The FileSystem tells the Classpath whether to behave as a module or regular class
* path via {@link Classpath#acceptModule(IModule)}.
*
* Sub types of classpath are responsible for appropriate behavior based on this.
*/
public interface Classpath extends IModulePathEntry {
char[][][] findTypeNames(String qualifiedPackageName, String moduleName);
NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName);
NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly);
boolean isPackage(String qualifiedPackageName, /*@Nullable*/String moduleName);
default boolean hasModule() { return getModule() != null; }
default boolean hasCUDeclaringPackage(String qualifiedPackageName, Function pkgNameExtractor) {
return hasCompilationUnit(qualifiedPackageName, null);
}
/**
* Return a list of the jar file names defined in the Class-Path section
* of the jar file manifest if any, null else. Only ClasspathJar (and
* extending classes) instances may return a non-null result.
* @param problemReporter problem reporter with which potential
* misconfiguration issues are raised
* @return a list of the jar file names defined in the Class-Path
* section of the jar file manifest if any
*/
List fetchLinkedJars(ClasspathSectionProblemReporter problemReporter);
/**
* This method resets the environment. The resulting state is equivalent to
* a new name environment without creating a new object.
*/
void reset();
/**
* Return a normalized path for file based classpath entries. This is an
* absolute path in which file separators are transformed to the
* platform-agnostic '/', ending with a '/' for directories. This is an
* absolute path in which file separators are transformed to the
* platform-agnostic '/', deprived from the '.jar' (resp. '.zip')
* extension for jar (resp. zip) files.
* @return a normalized path for file based classpath entries
*/
char[] normalizedPath();
/**
* Return the path for file based classpath entries. This is an absolute path
* ending with a file separator for directories, an absolute path including the '.jar'
* (resp. '.zip') extension for jar (resp. zip) files.
* @return the path for file based classpath entries
*/
String getPath();
/**
* Initialize the entry
*/
void initialize() throws IOException;
/**
* Can the current location provide an external annotation file for the given type?
* @param qualifiedTypeName type name in qualified /-separated notation.
*/
boolean hasAnnotationFileFor(String qualifiedTypeName);
/**
* Accepts to represent a module location with the given module description.
*/
public void acceptModule(IModule module);
public String getDestinationPath();
Collection getModuleNames(Collection limitModules);
Collection getModuleNames(Collection limitModules, Function getModule);
default boolean forbidsExportFrom(String modName) { return false; }
}
public interface ClasspathSectionProblemReporter {
void invalidClasspathSection(String jarFilePath);
void multipleClasspathSections(String jarFilePath);
}
/**
* This class is defined how to normalize the classpath entries.
* It removes duplicate entries.
*/
public static class ClasspathNormalizer {
/**
* Returns the normalized classpath entries (no duplicate).
* The given classpath entries are FileSystem.Classpath. We check the getPath() in order to find
* duplicate entries.
*
* @param classpaths the given classpath entries
* @return the normalized classpath entries
*/
public static ArrayList normalize(ArrayList classpaths) {
ArrayList normalizedClasspath = new ArrayList<>();
HashSet cache = new HashSet<>();
for (Classpath classpath : classpaths) {
if (!cache.contains(classpath)) {
normalizedClasspath.add(classpath);
cache.add(classpath);
}
}
return normalizedClasspath;
}
}
protected Classpath[] classpaths;
// Used only in single-module mode when the module descriptor is
// provided via command line.
protected IModule module;
Set knownFileNames;
protected boolean annotationsFromClasspath; // should annotation files be read from the classpath (vs. explicit separate path)?
private static HashMap JRT_CLASSPATH_CACHE = null;
protected Map moduleLocations = new HashMap<>();
private Consumer nameEnvironmentAnswerListener; // a listener for findType* answers
/** Tasks resulting from --add-reads or --add-exports command line options. */
Map moduleUpdates = new HashMap<>();
static boolean isJRE12Plus = false;
private boolean hasLimitModules = false;
static {
try {
isJRE12Plus = SourceVersion.valueOf("RELEASE_12") != null; //$NON-NLS-1$
} catch(IllegalArgumentException iae) {
// fall back to default
}
}
/*
classPathNames is a collection is Strings representing the full path of each class path
initialFileNames is a collection is Strings, the trailing '.java' will be removed if its not already.
*/
public FileSystem(String[] classpathNames, String[] initialFileNames, String encoding) {
this(classpathNames, initialFileNames, encoding, null, null);
}
public FileSystem(String[] classpathNames, String[] initialFileNames, String encoding, String release) {
this(classpathNames, initialFileNames, encoding, null, release);
}
protected FileSystem(String[] classpathNames, String[] initialFileNames, String encoding, Collection limitModules) {
this(classpathNames,initialFileNames, encoding, limitModules, null);
}
protected FileSystem(String[] classpathNames, String[] initialFileNames, String encoding, Collection limitModules, String release) {
final int classpathSize = classpathNames.length;
this.classpaths = new Classpath[classpathSize];
int counter = 0;
this.hasLimitModules = limitModules != null && !limitModules.isEmpty();
for (int i = 0; i < classpathSize; i++) {
Classpath classpath = getClasspath(classpathNames[i], encoding, null, null, release);
try {
classpath.initialize();
for (String moduleName : classpath.getModuleNames(limitModules))
this.moduleLocations.put(moduleName, classpath);
this.classpaths[counter++] = classpath;
} catch (IOException e) {
String error = "Failed to init " + classpath; //$NON-NLS-1$
if (JRTUtil.PROPAGATE_IO_ERRORS) {
throw new IllegalStateException(error, e);
} else {
System.err.println(error);
e.printStackTrace();
}
}
}
if (counter != classpathSize) {
System.arraycopy(this.classpaths, 0, (this.classpaths = new Classpath[counter]), 0, counter);
}
initializeKnownFileNames(initialFileNames);
}
protected FileSystem(Classpath[] paths, String[] initialFileNames, boolean annotationsFromClasspath, Set limitedModules) {
final int length = paths.length;
int counter = 0;
this.classpaths = new FileSystem.Classpath[length];
this.hasLimitModules = limitedModules != null && !limitedModules.isEmpty();
for (int i = 0; i < length; i++) {
final Classpath classpath = paths[i];
try {
classpath.initialize();
for (String moduleName : classpath.getModuleNames(limitedModules))
this.moduleLocations.put(moduleName, classpath);
this.classpaths[counter++] = classpath;
} catch(InvalidPathException exception) {
// JRE 9 could throw an IAE if the linked JAR paths have invalid chars, such as ":"
// ignore
} catch (NoSuchFileException e) {
// we don't warn about inexisting jars (javac does the same as us)
// see org.eclipse.jdt.core.tests.compiler.regression.BatchCompilerTest.test017b()
} catch (IOException e) {
String error = "Failed to init " + classpath; //$NON-NLS-1$
if (JRTUtil.PROPAGATE_IO_ERRORS) {
throw new IllegalStateException(error, e);
} else {
System.err.println(error);
e.printStackTrace();
}
}
}
if (counter != length) {
// should not happen
System.arraycopy(this.classpaths, 0, (this.classpaths = new FileSystem.Classpath[counter]), 0, counter);
}
initializeModuleLocations(limitedModules);
initializeKnownFileNames(initialFileNames);
this.annotationsFromClasspath = annotationsFromClasspath;
}
private void initializeModuleLocations(Set limitedModules) {
// First create the mapping of all module/Classpath
// since the second iteration of getModuleNames() can't be relied on for
// to get the right origin of module
if (limitedModules == null) {
for (Classpath c : this.classpaths) {
for (String moduleName : c.getModuleNames(null))
this.moduleLocations.put(moduleName, c);
}
} else {
Map moduleMap = new HashMap<>();
for (Classpath c : this.classpaths) {
for (String moduleName : c.getModuleNames(null)) {
moduleMap.put(moduleName, c);
}
}
for (Classpath c : this.classpaths) {
for (String moduleName : c.getModuleNames(limitedModules, m -> getModuleFromEnvironment(m.toCharArray()))) {
Classpath classpath = moduleMap.get(moduleName);
this.moduleLocations.put(moduleName, classpath);
}
}
}
}
protected FileSystem(Classpath[] paths, String[] initialFileNames, boolean annotationsFromClasspath) {
this(paths, initialFileNames, annotationsFromClasspath, null);
}
public static Classpath getClasspath(String classpathName, String encoding, AccessRuleSet accessRuleSet) {
return getClasspath(classpathName, encoding, false, accessRuleSet, null, null, null);
}
public static Classpath getClasspath(String classpathName, String encoding, AccessRuleSet accessRuleSet, Map options, String release) {
return getClasspath(classpathName, encoding, false, accessRuleSet, null, options, release);
}
public static Classpath getJrtClasspath(String jdkHome, String encoding, AccessRuleSet accessRuleSet, Map options) {
return new ClasspathJrt(new File(convertPathSeparators(jdkHome)), true, accessRuleSet, null);
}
public static Classpath getOlderSystemRelease(String jdkHome, String release, AccessRuleSet accessRuleSet) {
return isJRE12Plus ?
new ClasspathJep247Jdk12(new File(convertPathSeparators(jdkHome)), release, accessRuleSet) :
new ClasspathJep247(new File(convertPathSeparators(jdkHome)), release, accessRuleSet);
}
public static Classpath getClasspath(String classpathName, String encoding,
boolean isSourceOnly, AccessRuleSet accessRuleSet,
String destinationPath, Map options, String release) {
Classpath result = null;
File file = new File(convertPathSeparators(classpathName));
if (file.isDirectory()) {
if (file.exists()) {
result = new ClasspathDirectory(file, encoding,
isSourceOnly ? ClasspathLocation.SOURCE :
ClasspathLocation.SOURCE | ClasspathLocation.BINARY,
accessRuleSet,
destinationPath == null || destinationPath == Main.NONE ?
destinationPath : // keep == comparison valid
convertPathSeparators(destinationPath), options);
}
} else {
int format = Util.archiveFormat(classpathName);
if (format == Util.ZIP_FILE) {
if (isSourceOnly) {
// source only mode
result = new ClasspathSourceJar(file, true, accessRuleSet,
encoding,
destinationPath == null || destinationPath == Main.NONE ?
destinationPath : // keep == comparison valid
convertPathSeparators(destinationPath));
} else if (destinationPath == null) {
// class file only mode
if (Util.isJrt(classpathName)) {
if (JRT_CLASSPATH_CACHE == null) {
JRT_CLASSPATH_CACHE = new HashMap<>();
} else {
result = JRT_CLASSPATH_CACHE.get(file);
}
if (result == null) {
result = new ClasspathJrt(file, true, accessRuleSet, null);
try {
result.initialize();
} catch (IOException e) {
// Broken entry, but let clients have it anyway.
}
JRT_CLASSPATH_CACHE.put(file, result);
}
} else {
result =
(release == null) ?
new ClasspathJar(file, true, accessRuleSet, null) :
new ClasspathMultiReleaseJar(file, true, accessRuleSet, destinationPath, release);
}
}
} else if (format == Util.JMOD_FILE) {
return new ClasspathJmod(file, true, accessRuleSet, null);
}
}
return result;
}
private void initializeKnownFileNames(String[] initialFileNames) {
if (initialFileNames == null) {
this.knownFileNames = new HashSet<>(0);
return;
}
this.knownFileNames = new HashSet<>(initialFileNames.length * 2);
for (int i = initialFileNames.length; --i >= 0;) {
File compilationUnitFile = new File(initialFileNames[i]);
char[] fileName = null;
try {
fileName = compilationUnitFile.getCanonicalPath().toCharArray();
} catch (IOException e) {
// this should not happen as the file exists
continue;
}
char[] matchingPathName = null;
final int lastIndexOf = CharOperation.lastIndexOf('.', fileName);
if (lastIndexOf != -1) {
fileName = CharOperation.subarray(fileName, 0, lastIndexOf);
}
CharOperation.replace(fileName, '\\', '/');
boolean globalPathMatches = false;
// the most nested path should be the selected one
for (Classpath classpath : this.classpaths) {
char[] matchCandidate = classpath.normalizedPath();
boolean currentPathMatch = false;
if (classpath instanceof ClasspathDirectory
&& CharOperation.prefixEquals(matchCandidate, fileName)) {
currentPathMatch = true;
if (matchingPathName == null) {
matchingPathName = matchCandidate;
} else {
if (currentPathMatch) {
// we have a second source folder that matches the path of the source file
if (matchCandidate.length > matchingPathName.length) {
// we want to preserve the shortest possible path
matchingPathName = matchCandidate;
}
} else {
// we want to preserve the shortest possible path
if (!globalPathMatches && matchCandidate.length < matchingPathName.length) {
matchingPathName = matchCandidate;
}
}
}
if (currentPathMatch) {
globalPathMatches = true;
}
}
}
if (matchingPathName == null) {
this.knownFileNames.add(new String(fileName)); // leave as is...
} else {
this.knownFileNames.add(new String(CharOperation.subarray(fileName, matchingPathName.length, fileName.length)));
}
matchingPathName = null;
}
}
/** TESTS ONLY */
public void scanForModules(Parser parser) {
for (Classpath classpath : this.classpaths) {
File file = new File(classpath.getPath());
IModule iModule = ModuleFinder.scanForModule(classpath, file, parser, false, null);
if (iModule != null)
this.moduleLocations.put(String.valueOf(iModule.name()), classpath);
}
}
@Override
public void cleanup() {
for (Classpath classpath : this.classpaths)
classpath.reset();
}
private static String convertPathSeparators(String path) {
return File.separatorChar == '/'
? path.replace('\\', '/')
: path.replace('/', '\\');
}
@SuppressWarnings("resource") // don't close classpathEntry.zipFile, which we don't own
private NameEnvironmentAnswer findClass(String qualifiedTypeName, char[] typeName, boolean asBinaryOnly, /*NonNull*/char[] moduleName) {
NameEnvironmentAnswer answer = internalFindClass(qualifiedTypeName, typeName, asBinaryOnly, moduleName);
if (this.annotationsFromClasspath && answer != null && answer.getBinaryType() instanceof ClassFileReader) {
for (Classpath classpathEntry : this.classpaths) {
if (classpathEntry.hasAnnotationFileFor(qualifiedTypeName)) {
// in case of 'this.annotationsFromClasspath' we indeed search for .eea entries inside the main zipFile of the entry:
ZipFile zip = classpathEntry instanceof ClasspathJar ? ((ClasspathJar) classpathEntry).zipFile : null;
boolean shouldClose = false;
try {
if (zip == null) {
zip = ExternalAnnotationDecorator.getAnnotationZipFile(classpathEntry.getPath(), null);
shouldClose = true;
}
answer.setBinaryType(ExternalAnnotationDecorator.create(answer.getBinaryType(), classpathEntry.getPath(),
qualifiedTypeName, zip));
return notify(answer);
} catch (IOException e) {
// ignore broken entry, keep searching
} finally {
if (shouldClose && zip != null)
try {
zip.close();
} catch (IOException e) { /* nothing */ }
}
}
}
// globally configured (annotationsFromClasspath), but no .eea found, decorate in order to answer NO_EEA_FILE:
answer.setBinaryType(new ExternalAnnotationDecorator(answer.getBinaryType(), null));
}
return notify(answer);
}
private NameEnvironmentAnswer notify(NameEnvironmentAnswer answer) {
if(answer == null) {
return answer;
}
Consumer listener = this.nameEnvironmentAnswerListener;
if(listener != null) {
listener.accept(answer);
}
return answer;
}
private NameEnvironmentAnswer internalFindClass(String qualifiedTypeName, char[] typeName, boolean asBinaryOnly, /*NonNull*/char[] moduleName) {
if (this.knownFileNames.contains(qualifiedTypeName)) return null; // looking for a file which we know was provided at the beginning of the compilation
String qualifiedBinaryFileName = qualifiedTypeName + SUFFIX_STRING_class;
String qualifiedPackageName =
qualifiedTypeName.length() == typeName.length
? Util.EMPTY_STRING
: qualifiedBinaryFileName.substring(0, qualifiedTypeName.length() - typeName.length - 1);
LookupStrategy strategy = LookupStrategy.get(moduleName);
if (strategy == LookupStrategy.Named) {
if (this.moduleLocations != null) {
// searching for a specific named module:
String moduleNameString = String.valueOf(moduleName);
Classpath classpath = this.moduleLocations.get(moduleNameString);
if (classpath != null) {
return classpath.findClass(typeName, qualifiedPackageName, moduleNameString, qualifiedBinaryFileName);
}
}
return null;
}
String qp2 = File.separatorChar == '/' ? qualifiedPackageName : qualifiedPackageName.replace('/', File.separatorChar);
NameEnvironmentAnswer suggestedAnswer = null;
if (qualifiedPackageName == qp2) {
for (Classpath classpath : this.classpaths) {
if (!strategy.matches(classpath, Classpath::hasModule))
continue;
NameEnvironmentAnswer answer = classpath.findClass(typeName, qualifiedPackageName, null, qualifiedBinaryFileName, asBinaryOnly);
if (answer != null) {
if (answer.moduleName() != null && !this.moduleLocations.containsKey(String.valueOf(answer.moduleName())))
continue; // type belongs to an unobservable module
if (!answer.ignoreIfBetter()) {
if (answer.isBetter(suggestedAnswer))
return answer;
} else if (answer.isBetter(suggestedAnswer))
// remember suggestion and keep looking
suggestedAnswer = answer;
}
}
} else {
String qb2 = qualifiedBinaryFileName.replace('/', File.separatorChar);
for (Classpath p : this.classpaths) {
if (!strategy.matches(p, Classpath::hasModule))
continue;
NameEnvironmentAnswer answer = !(p instanceof ClasspathDirectory)
? p.findClass(typeName, qualifiedPackageName, null, qualifiedBinaryFileName, asBinaryOnly)
: p.findClass(typeName, qp2, null, qb2, asBinaryOnly);
if (answer != null) {
if (answer.moduleName() != null && !this.moduleLocations.containsKey(String.valueOf(answer.moduleName())))
continue; // type belongs to an unobservable module
if (!answer.ignoreIfBetter()) {
if (answer.isBetter(suggestedAnswer))
return answer;
} else if (answer.isBetter(suggestedAnswer))
// remember suggestion and keep looking
suggestedAnswer = answer;
}
}
}
return suggestedAnswer;
}
@Override
public NameEnvironmentAnswer findType(char[][] compoundName, char[] moduleName) {
if (compoundName != null)
return findClass(
new String(CharOperation.concatWith(compoundName, '/')),
compoundName[compoundName.length - 1],
false,
moduleName);
return null;
}
public char[][][] findTypeNames(char[][] packageName) {
char[][][] result = null;
if (packageName != null) {
String qualifiedPackageName = new String(CharOperation.concatWith(packageName, '/'));
String qualifiedPackageName2 = File.separatorChar == '/' ? qualifiedPackageName : qualifiedPackageName.replace('/', File.separatorChar);
if (qualifiedPackageName == qualifiedPackageName2) {
for (Classpath classpath : this.classpaths) {
char[][][] answers = classpath.findTypeNames(qualifiedPackageName, null);
if (answers != null) {
// concat with previous answers
if (result == null) {
result = answers;
} else {
int resultLength = result.length;
int answersLength = answers.length;
System.arraycopy(result, 0, (result = new char[answersLength + resultLength][][]), 0, resultLength);
System.arraycopy(answers, 0, result, resultLength, answersLength);
}
}
}
} else {
for (Classpath p : this.classpaths) {
char[][][] answers = !(p instanceof ClasspathDirectory) ? p.findTypeNames(qualifiedPackageName, null)
: p.findTypeNames(qualifiedPackageName2, null);
if (answers != null) {
// concat with previous answers
if (result == null) {
result = answers;
} else {
int resultLength = result.length;
int answersLength = answers.length;
System.arraycopy(result, 0, (result = new char[answersLength + resultLength][][]), 0,
resultLength);
System.arraycopy(answers, 0, result, resultLength, answersLength);
}
}
}
}
}
return result;
}
@Override
public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName, char[] moduleName) {
if (typeName != null)
return findClass(
new String(CharOperation.concatWith(packageName, typeName, '/')),
typeName,
false,
moduleName);
return null;
}
@Override
public char[][] getModulesDeclaringPackage(char[][] packageName, char[] moduleName) {
String qualifiedPackageName = new String(CharOperation.concatWith(packageName, '/'));
String moduleNameString = String.valueOf(moduleName);
LookupStrategy strategy = LookupStrategy.get(moduleName);
if (strategy == LookupStrategy.Named) {
if (this.moduleLocations != null) {
// specific search in a given module:
Classpath classpath = this.moduleLocations.get(moduleNameString);
if (classpath != null) {
if (classpath.isPackage(qualifiedPackageName, moduleNameString))
return new char[][] {moduleName};
}
}
return null;
}
// search the entire environment and answer which modules declare that package:
char[][] allNames = null;
boolean hasUnobserable = false;
for (Classpath cp : this.classpaths) {
if (strategy.matches(cp, Classpath::hasModule)) {
if (strategy == LookupStrategy.Unnamed) {
// short-cut
if (cp.isPackage(qualifiedPackageName, moduleNameString))
return new char[][] { ModuleBinding.UNNAMED };
} else {
char[][] declaringModules = cp.getModulesDeclaringPackage(qualifiedPackageName, null);
if (declaringModules != null) {
if (cp instanceof ClasspathJrt && this.hasLimitModules) {
declaringModules = filterModules(declaringModules);
hasUnobserable |= declaringModules == null;
}
if (allNames == null)
allNames = declaringModules;
else
allNames = CharOperation.arrayConcat(allNames, declaringModules);
}
}
}
}
if (allNames == null && hasUnobserable)
return new char[][] { ModuleBinding.UNOBSERVABLE };
return allNames;
}
private char[][] filterModules(char[][] declaringModules) {
char[][] filtered = Arrays.stream(declaringModules).filter(m -> this.moduleLocations.containsKey(new String(m))).toArray(char[][]::new);
if (filtered.length == 0)
return null;
return filtered;
}
private Parser getParser() {
Map opts = new HashMap<>();
opts.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_9);
return new Parser(
new ProblemReporter(DefaultErrorHandlingPolicies.exitOnFirstError(), new CompilerOptions(opts), new DefaultProblemFactory(Locale.getDefault())),
false);
}
@Override
public boolean hasCompilationUnit(char[][] qualifiedPackageName, char[] moduleName, boolean checkCUs) {
String qPackageName = String.valueOf(CharOperation.concatWith(qualifiedPackageName, '/'));
String moduleNameString = String.valueOf(moduleName);
LookupStrategy strategy = LookupStrategy.get(moduleName);
Parser parser = checkCUs ? getParser() : null;
Function pkgNameExtractor = sourceUnit -> {
String pkgName = null;
CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, 1);
char[][] name = parser.parsePackageDeclaration(sourceUnit.getContents(), compilationResult);
if (name != null) {
pkgName = CharOperation.toString(name);
}
return pkgName;
};
switch (strategy) {
case Named:
if (this.moduleLocations != null) {
Classpath location = this.moduleLocations.get(moduleNameString);
if (location != null)
return checkCUs ? location.hasCUDeclaringPackage(qPackageName, pkgNameExtractor)
: location.hasCompilationUnit(qPackageName, moduleNameString);
}
return false;
default:
for (Classpath location : this.classpaths) {
if (strategy.matches(location, Classpath::hasModule))
if (location.hasCompilationUnit(qPackageName, moduleNameString))
return true;
}
return false;
}
}
@Override
public IModule getModule(char[] name) {
if (this.module != null && CharOperation.equals(name, this.module.name())) {
return this.module;
}
if (this.moduleLocations.containsKey(new String(name))) {
for (Classpath classpath : this.classpaths) {
IModule mod = classpath.getModule(name);
if (mod != null) {
return mod;
}
}
}
return null;
}
public IModule getModuleFromEnvironment(char[] name) {
if (this.module != null && CharOperation.equals(name, this.module.name())) {
return this.module;
}
for (Classpath classpath : this.classpaths) {
IModule mod = classpath.getModule(name);
if (mod != null) {
return mod;
}
}
return null;
}
@Override
public char[][] getAllAutomaticModules() {
Set set = new HashSet<>();
for (Classpath classpath : this.classpaths) {
if (classpath.isAutomaticModule()) {
set.add(classpath.getModule().name());
}
}
return set.toArray(new char[set.size()][]);
}
@Override
public char[][] listPackages(char[] moduleName) {
switch (LookupStrategy.get(moduleName)) {
case Named:
Classpath classpath = this.moduleLocations.get(new String(moduleName));
if (classpath != null)
return classpath.listPackages();
return CharOperation.NO_CHAR_CHAR;
default:
throw new UnsupportedOperationException("can list packages only of a named module"); //$NON-NLS-1$
}
}
void addModuleUpdate(String moduleName, Consumer update, UpdateKind kind) {
UpdatesByKind updates = this.moduleUpdates.get(moduleName);
if (updates == null) {
this.moduleUpdates.put(moduleName, updates = new UpdatesByKind());
}
updates.getList(kind, true).add(update);
}
@Override
public void applyModuleUpdates(IUpdatableModule compilerModule, IUpdatableModule.UpdateKind kind) {
char[] name = compilerModule.name();
if (name != ModuleBinding.UNNAMED) { // can't update the unnamed module
UpdatesByKind updates = this.moduleUpdates.get(String.valueOf(name));
if (updates != null) {
for (Consumer update : updates.getList(kind, false))
update.accept(compilerModule);
}
}
}
/**
* @param nameEnvironmentAnswerListener
* a listener for {@link NameEnvironmentAnswer} returned by findType*
methods; useful for
* tracking used/answered dependencies during compilation (may be null
to unset)
* @return a previously set listener (may be null
)
*/
public Consumer setNameEnvironmentAnswerListener(Consumer nameEnvironmentAnswerListener) {
Consumer existing = this.nameEnvironmentAnswerListener;
this.nameEnvironmentAnswerListener = nameEnvironmentAnswerListener;
return existing;
}
}