| /******************************************************************************* |
| * Copyright (c) 2016, 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.lookup; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.function.Supplier; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment; |
| import org.eclipse.jdt.internal.compiler.env.IUpdatableModule; |
| import org.eclipse.jdt.internal.compiler.util.HashtableOfPackage; |
| import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable; |
| import org.eclipse.jdt.internal.compiler.util.SimpleSetOfCharArray; |
| |
| /** |
| * This class serves a dual purpose |
| * <p> |
| * First, it canonically represents modules in the world of bindings. |
| * </p> |
| * <p> |
| * Secondly, it adds a graph layer on top of {@link LookupEnvironment}: |
| * ModuleBindins are linked through "read" edges as per JPMS (see java.lang.module.Configuration). |
| * Additionally, each ModuleBinding holds its own instance of LookupEnviroment, |
| * capturing all packages and types that are visible to the current module. |
| * As a subset of all visible types, the ModuleBinding knows the set of |
| * packages locally declared in this module. |
| * </p> |
| */ |
| public class ModuleBinding extends Binding implements IUpdatableModule { |
| |
| /** Name of the unnamed module. */ |
| public static final char[] UNNAMED = "".toCharArray(); //$NON-NLS-1$ |
| /** Name to represent unnamed modules in --add-exports & --add-reads options. */ |
| public static final char[] ALL_UNNAMED = "ALL-UNNAMED".toCharArray(); //$NON-NLS-1$ |
| /** Module name for package/type lookup that doesn't care about modules. */ |
| public static final char[] ANY = "".toCharArray(); //$NON-NLS-1$ |
| /** Module name for package/type lookup that should look into all named modules. */ |
| public static final char[] ANY_NAMED = "".toCharArray(); //$NON-NLS-1$ |
| |
| public static class UnNamedModule extends ModuleBinding { |
| |
| private static final char[] UNNAMED_READABLE_NAME = "<unnamed>".toCharArray(); //$NON-NLS-1$ |
| |
| @SuppressWarnings("synthetic-access") |
| UnNamedModule(LookupEnvironment env) { |
| super(env); |
| } |
| @Override |
| public ModuleBinding[] getAllRequiredModules() { |
| return Binding.NO_MODULES; |
| } |
| @Override |
| public boolean canAccess(PackageBinding pkg) { |
| ModuleBinding mod = pkg.enclosingModule; |
| if (mod != null && mod != this) |
| return mod.isPackageExportedTo(pkg, this); |
| return true; |
| } |
| @Override |
| public boolean isPackageExportedTo(PackageBinding pkg, ModuleBinding client) { |
| // per JLS 7.7.5 an unnamed module exports all its packages |
| return pkg.isDeclaredIn(this) && pkg.hasCompilationUnit(false); |
| } |
| @Override |
| public boolean isUnnamed() { |
| return true; |
| } |
| @Override |
| public char[] nameForLookup() { |
| return ANY; |
| } |
| @Override |
| public char[] readableName() { |
| return UNNAMED_READABLE_NAME; |
| } |
| @Override |
| public String toString() { |
| return "The Unnamed Module"; //$NON-NLS-1$ |
| } |
| } |
| public char[] moduleName; |
| protected ModuleBinding[] requires; |
| protected ModuleBinding[] requiresTransitive; |
| protected PackageBinding[] exportedPackages; |
| private Map<PackageBinding,SimpleSetOfCharArray> exportRestrictions; // RHS is unresolved names, because unresolvable names are legal in this position |
| protected PackageBinding[] openedPackages; |
| private Map<PackageBinding,SimpleSetOfCharArray> openRestrictions; // RHS is unresolved names, because unresolvable names are legal in this position |
| protected TypeBinding[] uses; |
| protected TypeBinding[] services; |
| public Map<TypeBinding,TypeBinding[]> implementations; |
| public char[] mainClassName; |
| private SimpleSetOfCharArray packageNames; |
| public int modifiers; |
| public LookupEnvironment environment; |
| public long tagBits; |
| public int defaultNullness = NO_NULL_DEFAULT; |
| ModuleBinding[] requiredModules = null; |
| boolean isAuto = false; |
| private boolean[] isComplete = new boolean[UpdateKind.values().length]; |
| private Set<ModuleBinding> transitiveRequires; |
| boolean isPackageLookupActive = false; // to prevent cyclic lookup caused by synthetic reads edges on behalf of auto-modules. |
| SimpleLookupTable storedAnnotations = null; |
| |
| /** |
| * Packages declared in this module (indexed by qualified name). |
| * We consider a package as declared in a module, |
| * if a compilation unit associated with the module |
| * declares the package or a subpackage thereof. |
| */ |
| public HashtableOfPackage declaredPackages; |
| |
| /** Constructor for the unnamed module. */ |
| private ModuleBinding(LookupEnvironment env) { |
| this.moduleName = ModuleBinding.UNNAMED; |
| this.environment = env; |
| this.requires = Binding.NO_MODULES; |
| this.requiresTransitive = Binding.NO_MODULES; |
| this.exportedPackages = Binding.NO_PACKAGES; |
| this.openedPackages = Binding.NO_PACKAGES; |
| this.declaredPackages = new HashtableOfPackage(0); |
| Arrays.fill(this.isComplete, true); |
| } |
| /* For error binding and sub class SourceModuleBinding. */ |
| ModuleBinding(char[] moduleName) { |
| this.moduleName = moduleName; |
| this.requires = Binding.NO_MODULES; |
| this.requiresTransitive = Binding.NO_MODULES; |
| this.exportedPackages = Binding.NO_PACKAGES; |
| this.openedPackages = Binding.NO_PACKAGES; |
| this.uses = Binding.NO_TYPES; |
| this.services = Binding.NO_TYPES; |
| this.declaredPackages = new HashtableOfPackage(5); |
| } |
| |
| /* For sub class BinaryModuleBinding */ |
| protected ModuleBinding(char[] moduleName, LookupEnvironment existingEnvironment) { |
| this.moduleName = moduleName; |
| this.requires = Binding.NO_MODULES; |
| this.requiresTransitive = Binding.NO_MODULES; |
| this.environment = new LookupEnvironment(existingEnvironment.root, this); |
| this.declaredPackages = new HashtableOfPackage(5); |
| } |
| |
| public PackageBinding[] getExports() { |
| completeIfNeeded(UpdateKind.PACKAGE); |
| return this.exportedPackages; |
| } |
| public String[] getExportRestrictions(PackageBinding pack) { |
| completeIfNeeded(UpdateKind.PACKAGE); |
| if (this.exportRestrictions != null) { |
| SimpleSetOfCharArray set = this.exportRestrictions.get(pack); |
| if (set != null) { |
| char[][] names = new char[set.elementSize][]; |
| set.asArray(names); |
| return CharOperation.charArrayToStringArray(names); |
| } |
| } |
| return CharOperation.NO_STRINGS; |
| } |
| public PackageBinding[] getOpens() { |
| completeIfNeeded(UpdateKind.PACKAGE); |
| return this.openedPackages; |
| } |
| public String[] getOpenRestrictions(PackageBinding pack) { |
| completeIfNeeded(UpdateKind.PACKAGE); |
| if (this.openRestrictions != null) { |
| SimpleSetOfCharArray set = this.openRestrictions.get(pack); |
| if (set != null) { |
| char[][] names = new char[set.elementSize][]; |
| set.asArray(names); |
| return CharOperation.charArrayToStringArray(names); |
| } |
| } |
| return CharOperation.NO_STRINGS; |
| } |
| public TypeBinding[] getImplementations(TypeBinding binding) { |
| if (this.implementations != null) { |
| return this.implementations.get(binding); |
| } |
| return null; |
| } |
| public ModuleBinding[] getRequires() { |
| completeIfNeeded(UpdateKind.MODULE); |
| return this.requires; |
| } |
| public ModuleBinding[] getRequiresTransitive() { |
| completeIfNeeded(UpdateKind.MODULE); |
| return this.requiresTransitive; |
| } |
| |
| public TypeBinding[] getUses() { |
| return this.uses; |
| } |
| |
| public TypeBinding[] getServices() { |
| return this.services; |
| } |
| |
| private void completeIfNeeded(IUpdatableModule.UpdateKind kind) { |
| if (!this.isComplete[kind.ordinal()]) { |
| this.isComplete[kind.ordinal()] = true; |
| if (this.environment.nameEnvironment instanceof IModuleAwareNameEnvironment) { |
| ((IModuleAwareNameEnvironment) this.environment.nameEnvironment).applyModuleUpdates(this, kind); |
| } |
| } |
| } |
| |
| // --- Implement IUpdatableModule: --- |
| |
| @Override |
| public void addReads(char[] requiredModuleName) { |
| ModuleBinding requiredModule = this.environment.getModule(requiredModuleName); |
| if (requiredModule != null) { |
| int len = this.requires.length; |
| if (len == 0) { |
| this.requires = new ModuleBinding[] { requiredModule }; |
| } else { |
| System.arraycopy(this.requires, 0, this.requires = new ModuleBinding[len+1], 0, len); |
| this.requires[len] = requiredModule; |
| } |
| } else { |
| // TODO(SHMOD) report error |
| } |
| // update known packages: |
| HashtableOfPackage knownPackages = this.environment.knownPackages; |
| for (int i = 0; i < knownPackages.valueTable.length; i++) { |
| PackageBinding packageBinding = knownPackages.valueTable[i]; |
| if (packageBinding == null) continue; |
| PackageBinding newBinding = requiredModule.getVisiblePackage(packageBinding.compoundName); |
| newBinding = SplitPackageBinding.combine(newBinding, packageBinding, this); |
| if (packageBinding != newBinding) { |
| knownPackages.valueTable[i] = newBinding; |
| if (this.declaredPackages.containsKey(newBinding.readableName())) |
| this.declaredPackages.put(newBinding.readableName(), newBinding); |
| } |
| } |
| } |
| @Override |
| public void addExports(char[] packageName, char[][] targetModules) { |
| PackageBinding declaredPackage = getVisiblePackage(CharOperation.splitOn('.', packageName)); |
| if (declaredPackage != null && declaredPackage.isValidBinding()) |
| addResolvedExport(declaredPackage, targetModules); |
| } |
| |
| @Override |
| public void setMainClassName(char[] mainClassName) { |
| this.mainClassName = mainClassName; |
| } |
| |
| @Override |
| public void setPackageNames(SimpleSetOfCharArray packageNames) { |
| this.packageNames = packageNames; |
| } |
| |
| // for code gen: |
| /** @return array of names, which may contain nulls. */ |
| public char[][] getPackageNamesForClassFile() { |
| if (this.packageNames == null) |
| return null; |
| for (PackageBinding packageBinding : this.exportedPackages) |
| this.packageNames.add(packageBinding.readableName()); |
| for (PackageBinding packageBinding : this.openedPackages) |
| this.packageNames.add(packageBinding.readableName()); |
| if (this.implementations != null) |
| for (TypeBinding[] types : this.implementations.values()) |
| for (TypeBinding typeBinding : types) |
| this.packageNames.add(((ReferenceBinding)typeBinding).fPackage.readableName()); |
| return this.packageNames.values; |
| } |
| |
| // --- |
| |
| public void addResolvedExport(PackageBinding declaredPackage, char[][] targetModules) { |
| int len = this.exportedPackages.length; |
| if (declaredPackage == null || !declaredPackage.isValidBinding()) { |
| // FIXME(SHMOD) use a problem binding? See https://bugs.eclipse.org/518794#c13 |
| return; |
| } |
| if (len == 0) { |
| this.exportedPackages = new PackageBinding[] { declaredPackage }; |
| } else { |
| System.arraycopy(this.exportedPackages, 0, this.exportedPackages = new PackageBinding[len+1], 0, len); |
| this.exportedPackages[len] = declaredPackage; |
| } |
| declaredPackage.isExported = Boolean.TRUE; |
| recordExportRestrictions(declaredPackage, targetModules); |
| } |
| |
| public void addResolvedOpens(PackageBinding declaredPackage, char[][] targetModules) { |
| int len = this.openedPackages.length; |
| if (declaredPackage == null || !declaredPackage.isValidBinding()) { |
| // FIXME(SHMOD) use a problem binding? See https://bugs.eclipse.org/518794#c13 |
| return; |
| } |
| if (len == 0) { |
| this.openedPackages = new PackageBinding[] { declaredPackage }; |
| } else { |
| System.arraycopy(this.openedPackages, 0, this.openedPackages = new PackageBinding[len+1], 0, len); |
| this.openedPackages[len] = declaredPackage; |
| } |
| recordOpensRestrictions(declaredPackage, targetModules); |
| } |
| |
| protected void recordExportRestrictions(PackageBinding exportedPackage, char[][] targetModules) { |
| if (targetModules != null && targetModules.length > 0) { |
| SimpleSetOfCharArray targetModuleSet = null; |
| if (this.exportRestrictions != null) { |
| targetModuleSet = this.exportRestrictions.get(exportedPackage); |
| } else { |
| this.exportRestrictions = new HashMap<>(); |
| } |
| if (targetModuleSet == null) { |
| targetModuleSet = new SimpleSetOfCharArray(targetModules.length); |
| this.exportRestrictions.put(exportedPackage, targetModuleSet); |
| } |
| for (int i = 0; i < targetModules.length; i++) { |
| targetModuleSet.add(targetModules[i]); |
| } |
| } |
| } |
| |
| protected void recordOpensRestrictions(PackageBinding openedPackage, char[][] targetModules) { |
| if (targetModules != null && targetModules.length > 0) { |
| SimpleSetOfCharArray targetModuleSet = null; |
| if (this.openRestrictions != null) { |
| targetModuleSet = this.openRestrictions.get(openedPackage); |
| } else { |
| this.openRestrictions = new HashMap<>(); |
| } |
| if (targetModuleSet == null) { |
| targetModuleSet = new SimpleSetOfCharArray(targetModules.length); |
| this.openRestrictions.put(openedPackage, targetModuleSet); |
| } |
| for (int i = 0; i < targetModules.length; i++) { |
| targetModuleSet.add(targetModules[i]); |
| } |
| } |
| } |
| |
| Stream<ModuleBinding> getRequiredModules(boolean transitiveOnly) { |
| return Stream.of(transitiveOnly ? this.getRequiresTransitive() : this.getRequires()); |
| } |
| private void collectAllDependencies(Set<ModuleBinding> deps) { |
| getRequiredModules(false).forEach(m -> { |
| if (deps.add(m)) { |
| m.collectAllDependencies(deps); |
| } |
| }); |
| } |
| private void collectTransitiveDependencies(Set<ModuleBinding> deps) { |
| getRequiredModules(true).forEach(m -> { |
| if (deps.add(m)) { |
| m.collectTransitiveDependencies(deps); |
| } |
| }); |
| } |
| |
| // All modules required by this module, either directly or indirectly |
| public Supplier<Collection<ModuleBinding>> dependencyGraphCollector() { |
| return () -> getRequiredModules(false) |
| .collect(HashSet::new, |
| (set, mod) -> { |
| set.add(mod); |
| mod.collectAllDependencies(set); |
| }, |
| HashSet::addAll); |
| } |
| // All direct and transitive dependencies of this module |
| public Supplier<Collection<ModuleBinding>> dependencyCollector() { |
| return () -> getRequiredModules(false) |
| .collect(HashSet::new, |
| (set, mod) -> { |
| set.add(mod); |
| mod.collectTransitiveDependencies(set); |
| }, |
| HashSet::addAll); |
| } |
| |
| /** |
| * Get all the modules required by this module |
| * All required modules include modules explicitly specified as required in the module declaration |
| * as well as implicit dependencies - those specified as ' requires transitive ' by one of the |
| * dependencies |
| * |
| * @return |
| * An array of all required modules |
| */ |
| public ModuleBinding[] getAllRequiredModules() { |
| if (this.requiredModules != null) |
| return this.requiredModules; |
| |
| Collection<ModuleBinding> allRequires = dependencyCollector().get(); |
| if (allRequires.contains(this)) { |
| // TODO(SHMOD): report (when? where?) |
| return NO_MODULES; // avoid entering unbounded recursion due to cyclic requires |
| } |
| ModuleBinding javaBase = this.environment.javaBaseModule(); |
| // add java.base? |
| if (!CharOperation.equals(this.moduleName, TypeConstants.JAVA_BASE) // ... not if this *is* java.base |
| && javaBase != null // ... nor when java.base is absent |
| && javaBase != this.environment.UnNamedModule) // ..... or faked by the unnamed module |
| { |
| allRequires.add(javaBase); |
| } |
| return this.requiredModules = allRequires.size() > 0 ? allRequires.toArray(new ModuleBinding[allRequires.size()]) : Binding.NO_MODULES; |
| } |
| |
| /** Answer the name of this module. The unnamed module is identified by {@link #UNNAMED}. */ |
| @Override |
| public char[] name() { |
| return this.moduleName; |
| } |
| |
| /** |
| * Answer the name of this module as it should be used for package or type lookup. |
| * Unnamed and automatic modules answer {@link #ANY} or {@link #ANY_NAMED} resp., |
| * to signal that lookup should search in all accessible (named) modules. |
| */ |
| public char[] nameForLookup() { |
| return this.moduleName; |
| } |
| |
| /** |
| * Check if the specified package is owned by the current module and exported to the client module. |
| * True if the package appears in the list of exported packages and when the export is targeted, |
| * the module appears in the targets of the exports statement. |
| * @param pkg - the package whose visibility is to be checked |
| * @param client - the module that wishes to use the package |
| * @return true if the package is visible to the client module, false otherwise |
| */ |
| public boolean isPackageExportedTo(PackageBinding pkg, ModuleBinding client) { |
| // TODO(SHMOD): cache the result? |
| PackageBinding resolved = null; |
| if (pkg instanceof SplitPackageBinding) { |
| resolved = ((SplitPackageBinding) pkg).getIncarnation(this); |
| } else if (pkg.enclosingModule == this) { |
| resolved = pkg; |
| } |
| if (resolved != null) { |
| if (this.isAuto) { // all packages are exported by an automatic module |
| return pkg.enclosingModule == this; // no transitive export |
| } |
| PackageBinding[] initializedExports = getExports(); |
| for (int i = 0; i < initializedExports.length; i++) { |
| PackageBinding export = initializedExports[i]; |
| if (export.subsumes(resolved)) { |
| if (this.exportRestrictions != null) { |
| SimpleSetOfCharArray restrictions = this.exportRestrictions.get(export); |
| if (restrictions != null) { |
| if (client.isUnnamed()) |
| return restrictions.includes(ALL_UNNAMED); |
| else |
| return restrictions.includes(client.name()); |
| } |
| } |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return a package binding if there exists a package named name in this module's context and it can be seen by this module. |
| * A package can be seen by this module if it is declared in this module or any other module read by this module |
| * (JLS 7.4.3 for packages based on JLS 7.3 for compilation units). |
| * Package exports are not considered for visibility check (only when checking "uniquely visible" (JLS 7.4.3)). |
| * <p> |
| * The returned package may be a {@link SplitPackageBinding}, if more than one package of the given name is visible. |
| * </p> |
| * <p> |
| * When asked via the unnamed module or an automatic module all other named modules are considered visible. |
| * </p> |
| */ |
| public PackageBinding getTopLevelPackage(char[] name) { |
| // check caches: |
| PackageBinding binding = this.declaredPackages.get(name); |
| if (binding != null) |
| return binding; |
| binding = this.environment.getPackage0(name); |
| if (binding != null) |
| return binding; |
| binding = getVisiblePackage(null, name, true); |
| // remember: |
| if (binding != null) { |
| this.environment.knownPackages.put(name, binding); |
| binding = addPackage(binding, false); |
| } else { |
| this.environment.knownPackages.put(name, LookupEnvironment.TheNotFoundPackage); |
| } |
| return binding; |
| } |
| |
| PackageBinding getDeclaredPackage(char[][] parentName, char[] name) { |
| // check caches: |
| char[][] subPkgCompoundName = CharOperation.arrayConcat(parentName, name); |
| char[] fullFlatName = CharOperation.concatWith(subPkgCompoundName, '.'); |
| PackageBinding pkg = this.declaredPackages.get(fullFlatName); |
| if (pkg != null) |
| return pkg; |
| PackageBinding parent = parentName.length == 0 ? null : getVisiblePackage(parentName); |
| PackageBinding binding = new PackageBinding(subPkgCompoundName, parent, this.environment, this); |
| // remember |
| this.declaredPackages.put(fullFlatName, binding); |
| if (parent == null) { |
| this.environment.knownPackages.put(name, binding); |
| } |
| return binding; |
| } |
| // Given parent is visible in this module, see if there is sub package named name visible in this module |
| PackageBinding getVisiblePackage(PackageBinding parent, char[] name, boolean considerRequiredModules) { |
| // check caches: |
| char[][] parentName = parent == null ? CharOperation.NO_CHAR_CHAR : parent.compoundName; |
| char[][] subPkgCompoundName = CharOperation.arrayConcat(parentName, name); |
| char[] fullFlatName = CharOperation.concatWith(subPkgCompoundName, '.'); |
| PackageBinding pkg = this.declaredPackages.get(fullFlatName); |
| if (pkg != null) |
| return pkg; |
| if (parent != null) |
| pkg = parent.getPackage0(name); |
| else |
| pkg = this.environment.getPackage0(name); |
| if (pkg != null) { |
| if (pkg == LookupEnvironment.TheNotFoundPackage) |
| return null; |
| else |
| return addPackage(pkg, false); |
| } |
| |
| PackageBinding binding = null; |
| char[][] declaringModuleNames = null; |
| boolean packageMayBeIncomplete = !considerRequiredModules; |
| if (this.environment.useModuleSystem) { |
| IModuleAwareNameEnvironment moduleEnv = (IModuleAwareNameEnvironment) this.environment.nameEnvironment; |
| declaringModuleNames = moduleEnv.getUniqueModulesDeclaringPackage(parentName, name, nameForLookup()); |
| if (declaringModuleNames != null) { |
| if (CharOperation.containsEqual(declaringModuleNames, this.moduleName)) { |
| // declared here, not yet known, so create it now: |
| binding = new PackageBinding(subPkgCompoundName, parent, this.environment, this); |
| } else if (considerRequiredModules) { |
| // visible but foreign (when current is unnamed or auto): |
| for (char[] declaringModuleName : declaringModuleNames) { |
| ModuleBinding declaringModule = this.environment.root.getModule(declaringModuleName); |
| if (declaringModule != null) { |
| if (declaringModule.isPackageLookupActive) { |
| packageMayBeIncomplete = true; |
| } else { |
| PackageBinding declaredPackage = declaringModule.getDeclaredPackage(parentName, name); |
| if (declaredPackage != null) { |
| // don't add foreign package to 'parent' (below), but to its own parent: |
| if (declaredPackage.parent != null) |
| declaredPackage.parent.addPackage(declaredPackage, declaringModule); |
| parent = null; |
| // |
| binding = SplitPackageBinding.combine(declaredPackage, binding, this); |
| } |
| } |
| } |
| } |
| } |
| } |
| } else { |
| if (this.environment.nameEnvironment.isPackage(parentName, name)) |
| binding = new PackageBinding(subPkgCompoundName, parent, this.environment, this); |
| } |
| |
| // enrich with split-siblings from visible modules: |
| if (considerRequiredModules) { |
| binding = combineWithPackagesFromOtherRelevantModules(binding, subPkgCompoundName, declaringModuleNames); |
| } |
| if (binding == null || !binding.isValidBinding()) { |
| if (parent != null |
| && !packageMayBeIncomplete // don't remember package that may still lack some siblings |
| && !(parent instanceof SplitPackageBinding)) // don't store problem into SPB, because from different focus things may look differently |
| { |
| parent.knownPackages.put(name, binding == null ? LookupEnvironment.TheNotFoundPackage : binding); |
| } |
| return null; |
| } |
| // remember |
| if (parentName.length == 0) |
| binding.environment.knownPackages.put(name, binding); |
| else if (parent != null) |
| binding = parent.addPackage(binding, this); |
| return addPackage(binding, false); |
| } |
| |
| /** |
| * Answer the package of the given qualified name and visible in this module, |
| * or {@code null} if no such package exists. |
| * Accessibility (based on package exports) is <strong>not</strong> checked. |
| * <p> |
| * May answer a {@link SplitPackageBinding}. |
| * </p> |
| */ |
| public PackageBinding getVisiblePackage(char[][] qualifiedPackageName) { |
| return getVisiblePackage(qualifiedPackageName, true); |
| } |
| PackageBinding getVisiblePackage(char[][] qualifiedPackageName, boolean considerRequiredModules) { |
| if (qualifiedPackageName == null || qualifiedPackageName.length == 0) { |
| return this.environment.defaultPackage; |
| } |
| |
| PackageBinding parent = getTopLevelPackage(qualifiedPackageName[0]); |
| if (parent == null || parent == LookupEnvironment.TheNotFoundPackage) |
| return null; |
| |
| // check each sub package |
| for (int i = 1; i < qualifiedPackageName.length; i++) { |
| PackageBinding binding = getVisiblePackage(parent, qualifiedPackageName[i], considerRequiredModules); |
| if (binding == null || binding == LookupEnvironment.TheNotFoundPackage) { |
| return null; |
| } |
| parent = binding; |
| } |
| return parent; |
| } |
| |
| /** |
| * Answer a package, that is a member named <em>packageName</em> of the parent package |
| * named <em>parentPackageName</em>. |
| * Considers all packages that are visible to the current module, |
| * i.e., we consider locally declared packages and packages in all modules |
| * read by the current module. |
| * Accessibility (via package exports) is <strong>not</strong> checked. |
| */ |
| public PackageBinding getPackage(char[][] parentPackageName, char[] packageName) { |
| // Returns a package binding if there exists such a package in the context of this module and it is observable |
| // A package is observable if it is declared in this module or it is exported by some required module |
| if (parentPackageName == null || parentPackageName.length == 0) { |
| return getVisiblePackage(null, packageName, true); |
| } |
| PackageBinding binding = null; |
| PackageBinding parent = getVisiblePackage(parentPackageName); |
| if (parent != null && parent != LookupEnvironment.TheNotFoundPackage) { |
| binding = getVisiblePackage(parent, packageName, true); |
| } |
| if (binding != null) |
| return addPackage(binding, false); |
| return null; |
| } |
| |
| /** |
| * Check if the given package is declared in this module, |
| * and if so, remember this fact for later. |
| * The package can be a {@code SplitPackageBinding} in which case |
| * only one of its incarnations needs to be declared in this module. |
| * @param packageBinding the package to add |
| * @param checkForSplit if true then we should try to construct a split package from |
| * same named packages in required modules. |
| * @return the given package, possibly enriched to a {@link SplitPackageBinding} |
| */ |
| PackageBinding addPackage(PackageBinding packageBinding, boolean checkForSplit) { |
| if (packageBinding.isDeclaredIn(this)) { |
| char[] packageName = packageBinding.readableName(); |
| if (checkForSplit && this.environment.useModuleSystem) { |
| char[][] declaringModuleNames = null; |
| if (isUnnamed()) { |
| IModuleAwareNameEnvironment moduleEnv = (IModuleAwareNameEnvironment) this.environment.nameEnvironment; |
| declaringModuleNames = moduleEnv.getUniqueModulesDeclaringPackage(null, packageName, ANY); |
| } |
| packageBinding = combineWithPackagesFromOtherRelevantModules(packageBinding, packageBinding.compoundName, declaringModuleNames); |
| } |
| this.declaredPackages.put(packageName, packageBinding); |
| if (packageBinding.parent == null) { |
| this.environment.knownPackages.put(packageName, packageBinding); |
| } |
| } |
| return packageBinding; |
| } |
| |
| private PackageBinding combineWithPackagesFromOtherRelevantModules(PackageBinding currentBinding, char[][] compoundName, char[][] declaringModuleNames) { |
| boolean save = this.isPackageLookupActive; |
| this.isPackageLookupActive = true; |
| try { |
| char[] singleName = compoundName[compoundName.length-1]; |
| PackageBinding parent = currentBinding != null ? currentBinding.parent : null; |
| for (ModuleBinding moduleBinding : otherRelevantModules(declaringModuleNames)) { |
| if (!moduleBinding.isPackageLookupActive) { |
| PackageBinding nextBinding = parent != null |
| ? moduleBinding.getVisiblePackage(parent, singleName, false) |
| : moduleBinding.getVisiblePackage(compoundName, false); |
| currentBinding = SplitPackageBinding.combine(nextBinding, currentBinding, this); |
| } |
| } |
| return currentBinding; |
| } finally { |
| this.isPackageLookupActive = save; |
| } |
| } |
| |
| List<ModuleBinding> otherRelevantModules(char[][] declaringModuleNames) { |
| if (isUnnamed() && declaringModuleNames != null) { |
| // unnamed module reads all named modules, |
| // so all modules declaring the given package are relevant: |
| return Arrays.stream(declaringModuleNames) |
| .filter(modName -> modName != UNNAMED) |
| .map(modName -> this.environment.getModule(modName)) |
| .filter(Objects::nonNull) |
| .collect(Collectors.toList()); |
| } else { |
| return Arrays.asList(getAllRequiredModules()); |
| } |
| } |
| |
| /** |
| * Check if the given package is accessible by this module. True when the package is declared in |
| * this module or exported by some required module to this module. |
| * See {@link #isPackageExportedTo(PackageBinding, ModuleBinding)} |
| * |
| * @param pkg |
| * |
| * @return True, if the package is accessible by this module, false otherwise |
| */ |
| public boolean canAccess(PackageBinding pkg) { |
| if (pkg.isDeclaredIn(this)) |
| return true; |
| for (ModuleBinding requiredModule : getAllRequiredModules()) { |
| // If pkg is a SplitPackageBinding, we actually ask the intersection of all required modules |
| // and modules declaring the package, if any of them exports the package to this module. |
| // The intersection is computed when inside isPackageExportedTo we ask for pkg's incarnation in requiredModule. |
| if (requiredModule.isPackageExportedTo(pkg, ModuleBinding.this)) |
| return true; |
| // TODO(SHMOD): store export status in the PackageBinding? |
| } |
| return false; |
| } |
| @Override |
| public char[] computeUniqueKey(boolean isLeaf) { |
| return CharOperation.prepend('"', this.moduleName); |
| } |
| |
| @Override |
| public int kind() { |
| // |
| return Binding.MODULE; |
| } |
| |
| @Override |
| public char[] readableName() { |
| return this.moduleName; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(30); |
| if (isOpen()) |
| buffer.append("open "); //$NON-NLS-1$ |
| buffer.append("module " + new String(readableName())); //$NON-NLS-1$ |
| if (this.requires.length > 0) { |
| buffer.append("\n/* requires */\n"); //$NON-NLS-1$ |
| for (int i = 0; i < this.requires.length; i++) { |
| buffer.append("\n\t"); //$NON-NLS-1$ |
| if (this.requiresTransitive != null) { |
| for (ModuleBinding reqTrans : this.requiresTransitive) { |
| if (reqTrans == this.requires[i]) { |
| buffer.append("transitive "); //$NON-NLS-1$ |
| break; |
| } |
| } |
| } |
| buffer.append(this.requires[i].moduleName); |
| } |
| } else { |
| buffer.append("\nNo Requires"); //$NON-NLS-1$ |
| } |
| if (this.exportedPackages != null && this.exportedPackages.length > 0) { |
| buffer.append("\n/* exports */\n"); //$NON-NLS-1$ |
| for (int i = 0; i < this.exportedPackages.length; i++) { |
| PackageBinding export = this.exportedPackages[i]; |
| buffer.append("\n\t"); //$NON-NLS-1$ |
| if (export == null) { |
| buffer.append("<unresolved>"); //$NON-NLS-1$ |
| continue; |
| } |
| buffer.append(export.readableName()); |
| SimpleSetOfCharArray restrictions = this.exportRestrictions != null ? this.exportRestrictions.get(export) : null; |
| if (restrictions != null) { |
| buffer.append(" to "); //$NON-NLS-1$ |
| String sep = ""; //$NON-NLS-1$ |
| char[][] allNames = new char[restrictions.elementSize][]; |
| restrictions.asArray(allNames); |
| for (char[] targetModule : allNames) { |
| buffer.append(sep); |
| buffer.append(targetModule); |
| sep = ", "; //$NON-NLS-1$ |
| } |
| } |
| } |
| } else { |
| buffer.append("\nNo Exports"); //$NON-NLS-1$ |
| } |
| if (this.openedPackages != null && this.openedPackages.length > 0) { |
| buffer.append("\n/* exports */\n"); //$NON-NLS-1$ |
| for (int i = 0; i < this.openedPackages.length; i++) { |
| PackageBinding opens = this.openedPackages[i]; |
| buffer.append("\n\t"); //$NON-NLS-1$ |
| if (opens == null) { |
| buffer.append("<unresolved>"); //$NON-NLS-1$ |
| continue; |
| } |
| buffer.append(opens.readableName()); |
| SimpleSetOfCharArray restrictions = this.openRestrictions != null ? this.openRestrictions.get(opens) : null; |
| if (restrictions != null) { |
| buffer.append(" to "); //$NON-NLS-1$ |
| String sep = ""; //$NON-NLS-1$ |
| char[][] allNames = new char[restrictions.elementSize][]; |
| restrictions.asArray(allNames); |
| for (char[] targetModule : allNames) { |
| buffer.append(sep); |
| buffer.append(targetModule); |
| sep = ", "; //$NON-NLS-1$ |
| } |
| } |
| } |
| } else { |
| buffer.append("\nNo Opens"); //$NON-NLS-1$ |
| } |
| if (this.uses != null && this.uses.length > 0) { |
| buffer.append("\n/* uses /*\n"); //$NON-NLS-1$ |
| for (int i = 0; i < this.uses.length; i++) { |
| buffer.append("\n\t"); //$NON-NLS-1$ |
| buffer.append(this.uses[i].debugName()); |
| } |
| } else { |
| buffer.append("\nNo Uses"); //$NON-NLS-1$ |
| } |
| if (this.services != null && this.services.length > 0) { |
| buffer.append("\n/* Services */\n"); //$NON-NLS-1$ |
| for (int i = 0; i < this.services.length; i++) { |
| buffer.append("\n\t"); //$NON-NLS-1$ |
| buffer.append("provides "); //$NON-NLS-1$ |
| buffer.append(this.services[i].debugName()); |
| buffer.append(" with "); //$NON-NLS-1$ |
| if (this.implementations != null && this.implementations.containsKey(this.services[i])) { |
| String sep = ""; //$NON-NLS-1$ |
| for (TypeBinding impl : this.implementations.get(this.services[i])) { |
| buffer.append(sep).append(impl.debugName()); |
| sep = ", "; //$NON-NLS-1$ |
| } |
| } else { |
| buffer.append("<missing implementations>"); //$NON-NLS-1$ |
| } |
| } |
| } else { |
| buffer.append("\nNo Services"); //$NON-NLS-1$ |
| } |
| return buffer.toString(); |
| } |
| public boolean isUnnamed() { |
| return false; |
| } |
| public boolean isOpen() { |
| return (this.modifiers & ClassFileConstants.ACC_OPEN) != 0; |
| } |
| public boolean isDeprecated() { |
| return (this.tagBits & TagBits.AnnotationDeprecated) != 0; |
| } |
| public boolean hasUnstableAutoName() { |
| return false; |
| } |
| public boolean isTransitivelyRequired(ModuleBinding otherModule) { |
| if (this.transitiveRequires == null) { |
| Set<ModuleBinding> transitiveDeps = new HashSet<>(); |
| collectTransitiveDependencies(transitiveDeps); |
| this.transitiveRequires = transitiveDeps; |
| } |
| return this.transitiveRequires.contains(otherModule); |
| } |
| |
| public int getDefaultNullness() { |
| getAnnotationTagBits(); // ensure annotations are initialized |
| return this.defaultNullness; |
| } |
| SimpleLookupTable storedAnnotations(boolean forceInitialize, boolean forceStore) { |
| |
| if (forceInitialize && this.storedAnnotations == null) { |
| if (!this.environment.globalOptions.storeAnnotations && !forceStore) |
| return null; // not supported during this compile |
| this.storedAnnotations = new SimpleLookupTable(3); |
| } |
| return this.storedAnnotations; |
| } |
| public AnnotationHolder retrieveAnnotationHolder(Binding binding, boolean forceInitialization) { |
| SimpleLookupTable store = storedAnnotations(forceInitialization, false); |
| return store == null ? null : (AnnotationHolder) store.get(binding); |
| } |
| |
| AnnotationBinding[] retrieveAnnotations(Binding binding) { |
| AnnotationHolder holder = retrieveAnnotationHolder(binding, true); |
| return holder == null ? Binding.NO_ANNOTATIONS : holder.getAnnotations(); |
| } |
| |
| @Override |
| public void setAnnotations(AnnotationBinding[] annotations, boolean forceStore) { |
| storeAnnotations(this, annotations, forceStore); |
| } |
| void storeAnnotationHolder(Binding binding, AnnotationHolder holder) { |
| if (holder == null) { |
| SimpleLookupTable store = storedAnnotations(false, false); |
| if (store != null) |
| store.removeKey(binding); |
| } else { |
| SimpleLookupTable store = storedAnnotations(true, false); |
| if (store != null) |
| store.put(binding, holder); |
| } |
| } |
| |
| void storeAnnotations(Binding binding, AnnotationBinding[] annotations, boolean forceStore) { |
| AnnotationHolder holder = null; |
| if (annotations == null || annotations.length == 0) { |
| SimpleLookupTable store = storedAnnotations(false, forceStore); |
| if (store != null) |
| holder = (AnnotationHolder) store.get(binding); |
| if (holder == null) return; // nothing to delete |
| } else { |
| SimpleLookupTable store = storedAnnotations(true, forceStore); |
| if (store == null) return; // not supported |
| holder = (AnnotationHolder) store.get(binding); |
| if (holder == null) |
| holder = new AnnotationHolder(); |
| } |
| storeAnnotationHolder(binding, holder.setAnnotations(annotations)); |
| } |
| } |