| /******************************************************************************* |
| * Copyright (c) 2017, 2018 GK Software SE, 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: |
| * Stephan Herrmann - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.lookup; |
| |
| import java.util.HashMap; |
| 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.IBinaryAnnotation; |
| import org.eclipse.jdt.internal.compiler.env.IBinaryModule; |
| import org.eclipse.jdt.internal.compiler.env.IModule; |
| import org.eclipse.jdt.internal.compiler.env.IModule.IModuleReference; |
| import org.eclipse.jdt.internal.compiler.env.IModule.IPackageExport; |
| import org.eclipse.jdt.internal.compiler.env.IModule.IService; |
| import org.eclipse.jdt.internal.compiler.util.Util; |
| import org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment; |
| |
| public class BinaryModuleBinding extends ModuleBinding { |
| |
| private static class AutomaticModuleBinding extends ModuleBinding { |
| |
| boolean autoNameFromManifest; |
| |
| public AutomaticModuleBinding(IModule module, LookupEnvironment existingEnvironment) { |
| super(module.name(), existingEnvironment); |
| existingEnvironment.root.knownModules.put(this.moduleName, this); |
| this.isAuto = true; |
| this.autoNameFromManifest = module.isAutoNameFromManifest(); |
| this.requires = Binding.NO_MODULES; |
| this.requiresTransitive = Binding.NO_MODULES; |
| this.exportedPackages = Binding.NO_PACKAGES; |
| } |
| @Override |
| public boolean hasUnstableAutoName() { |
| return !this.autoNameFromManifest; |
| } |
| @Override |
| public ModuleBinding[] getRequiresTransitive() { |
| if (this.requiresTransitive == NO_MODULES) { |
| char[][] autoModules = ((IModuleAwareNameEnvironment)this.environment.nameEnvironment).getAllAutomaticModules(); |
| this.requiresTransitive = Stream.of(autoModules) |
| .filter(name -> !CharOperation.equals(name, this.moduleName)) |
| .map(name -> this.environment.getModule(name)).filter(m -> m != null) |
| .toArray(ModuleBinding[]::new); |
| } |
| return this.requiresTransitive; |
| } |
| @Override |
| public char[] nameForLookup() { |
| return ANY_NAMED; |
| } |
| } |
| |
| private IPackageExport[] unresolvedExports; |
| private IPackageExport[] unresolvedOpens; |
| private char[][] unresolvedUses; |
| private IService[] unresolvedProvides; |
| |
| /** |
| * Construct a named module from binary, could be an auto module - or from an info from Java Model. |
| * <p> |
| * <strong>precondition:</strong> module must be either IBinaryModule or IModule.AutoModule |
| * </p> |
| * <p> |
| * <strong>Side effects:</strong> adds the new module to root.knownModules and resolves its directives. |
| * </p> |
| */ |
| public static ModuleBinding create(IModule module, LookupEnvironment existingEnvironment) { |
| if (module.isAutomatic()) |
| return new AutomaticModuleBinding(module, existingEnvironment); |
| return new BinaryModuleBinding((IBinaryModule) module, existingEnvironment); |
| } |
| |
| private BinaryModuleBinding(IBinaryModule module, LookupEnvironment existingEnvironment) { |
| super(module.name(), existingEnvironment); |
| existingEnvironment.root.knownModules.put(this.moduleName, this); |
| cachePartsFrom(module); |
| } |
| |
| void cachePartsFrom(IBinaryModule module) { |
| if (module.isOpen()) |
| this.modifiers |= ClassFileConstants.ACC_OPEN; |
| this.tagBits |= module.getTagBits(); |
| |
| IModuleReference[] requiresReferences = module.requires(); |
| this.requires = new ModuleBinding[requiresReferences.length]; |
| this.requiresTransitive = new ModuleBinding[requiresReferences.length]; |
| int count = 0; |
| int transitiveCount = 0; |
| for (int i = 0; i < requiresReferences.length; i++) { |
| ModuleBinding requiredModule = this.environment.getModule(requiresReferences[i].name()); |
| if (requiredModule != null) { |
| this.requires[count++] = requiredModule; |
| if (requiresReferences[i].isTransitive()) |
| this.requiresTransitive[transitiveCount++] = requiredModule; |
| } |
| // TODO(SHMOD): handle null case |
| } |
| if (count < this.requiresTransitive.length) |
| System.arraycopy(this.requires, 0, this.requires = new ModuleBinding[count], 0, count); |
| if (transitiveCount < this.requiresTransitive.length) |
| System.arraycopy(this.requiresTransitive, 0, this.requiresTransitive = new ModuleBinding[transitiveCount], 0, transitiveCount); |
| |
| this.unresolvedExports = module.exports(); |
| this.unresolvedOpens = module.opens(); |
| this.unresolvedUses = module.uses(); |
| this.unresolvedProvides = module.provides(); |
| if (this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled) { |
| scanForNullDefaultAnnotation(module); |
| } |
| if ((this.tagBits & TagBits.AnnotationDeprecated) != 0 || this.environment.globalOptions.storeAnnotations) { |
| this.setAnnotations(BinaryTypeBinding.createAnnotations(module.getAnnotations(), this.environment, null), true); |
| } |
| } |
| |
| private void scanForNullDefaultAnnotation(IBinaryModule binaryModule) { |
| // trimmed-down version of BinaryTypeBinding.scanForNullDefaultAnnotation() |
| char[][] nonNullByDefaultAnnotationName = this.environment.getNonNullByDefaultAnnotationName(); |
| if (nonNullByDefaultAnnotationName == null) |
| return; // not well-configured to use null annotations |
| |
| IBinaryAnnotation[] annotations = binaryModule.getAnnotations(); |
| if (annotations != null) { |
| int nullness = NO_NULL_DEFAULT; |
| int length = annotations.length; |
| for (int i = 0; i < length; i++) { |
| char[] annotationTypeName = annotations[i].getTypeName(); |
| if (annotationTypeName[0] != Util.C_RESOLVED) |
| continue; |
| int typeBit = this.environment.getNullAnnotationBit(BinaryTypeBinding.signature2qualifiedTypeName(annotationTypeName)); |
| if (typeBit == TypeIds.BitNonNullByDefaultAnnotation) { |
| // using NonNullByDefault we need to inspect the details of the value() attribute: |
| nullness |= BinaryTypeBinding.getNonNullByDefaultValue(annotations[i], this.environment); |
| } |
| } |
| this.defaultNullness = nullness; |
| } |
| } |
| |
| @Override |
| public PackageBinding[] getExports() { |
| if (this.exportedPackages == null && this.unresolvedExports != null) |
| resolvePackages(); |
| return super.getExports(); |
| } |
| |
| @Override |
| public PackageBinding[] getOpens() { |
| if (this.openedPackages == null && this.unresolvedOpens != null) |
| resolvePackages(); |
| return super.getOpens(); |
| } |
| |
| private void resolvePackages() { |
| this.exportedPackages = new PackageBinding[this.unresolvedExports.length]; |
| int count = 0; |
| for (int i = 0; i < this.unresolvedExports.length; i++) { |
| IPackageExport export = this.unresolvedExports[i]; |
| PackageBinding declaredPackage = forcedGetExportedPackage(CharOperation.splitOn('.', export.name())); |
| if (declaredPackage != null) { |
| this.exportedPackages[count++] = declaredPackage; |
| if (declaredPackage instanceof SplitPackageBinding) |
| declaredPackage = ((SplitPackageBinding) declaredPackage).getIncarnation(this); |
| if (declaredPackage != null) { |
| declaredPackage.isExported = Boolean.TRUE; |
| recordExportRestrictions(declaredPackage, export.targets()); |
| } |
| } |
| } |
| if (count < this.exportedPackages.length) |
| System.arraycopy(this.exportedPackages, 0, this.exportedPackages = new PackageBinding[count], 0, count); |
| |
| this.openedPackages = new PackageBinding[this.unresolvedOpens.length]; |
| count = 0; |
| for (int i = 0; i < this.unresolvedOpens.length; i++) { |
| IPackageExport opens = this.unresolvedOpens[i]; |
| PackageBinding declaredPackage = getVisiblePackage(CharOperation.splitOn('.', opens.name())); |
| if (declaredPackage != null) { |
| this.openedPackages[count++] = declaredPackage; |
| if (declaredPackage instanceof SplitPackageBinding) |
| declaredPackage = ((SplitPackageBinding) declaredPackage).getIncarnation(this); |
| if (declaredPackage != null) { |
| recordOpensRestrictions(declaredPackage, opens.targets()); |
| } |
| } else { |
| // TODO(SHMOD): report incomplete module path? |
| } |
| } |
| if (count < this.openedPackages.length) |
| System.arraycopy(this.openedPackages, 0, this.openedPackages = new PackageBinding[count], 0, count); |
| } |
| |
| PackageBinding forcedGetExportedPackage(char[][] compoundName) { |
| // when resolving "exports" in a binary module we simply assume the package must exist, |
| // since this has been checked already when compiling that module. |
| PackageBinding binding = getVisiblePackage(compoundName); |
| if (binding != null) |
| return binding; |
| if (compoundName.length > 1) { |
| PackageBinding parent = forcedGetExportedPackage(CharOperation.subarray(compoundName, 0, compoundName.length-1)); |
| binding = new PackageBinding(compoundName, parent, this.environment, this); |
| parent.addPackage(binding, this, true); |
| return binding; |
| } |
| binding = new PackageBinding(compoundName[0], this.environment, this); |
| addPackage(binding, true); |
| return binding; |
| } |
| |
| @Override |
| public TypeBinding[] getUses() { |
| if (this.uses == null) { |
| this.uses = new TypeBinding[this.unresolvedUses.length]; |
| for (int i = 0; i < this.unresolvedUses.length; i++) |
| this.uses[i] = this.environment.getType(CharOperation.splitOn('.', this.unresolvedUses[i]), this); |
| } |
| return super.getUses(); |
| } |
| |
| @Override |
| public TypeBinding[] getServices() { |
| if (this.services == null) |
| resolveServices(); |
| return super.getServices(); |
| } |
| |
| @Override |
| public TypeBinding[] getImplementations(TypeBinding binding) { |
| if (this.implementations == null) |
| resolveServices(); |
| return super.getImplementations(binding); |
| } |
| private void resolveServices() { |
| this.services = new TypeBinding[this.unresolvedProvides.length]; |
| this.implementations = new HashMap<>(); |
| for (int i = 0; i < this.unresolvedProvides.length; i++) { |
| this.services[i] = this.environment.getType(CharOperation.splitOn('.', this.unresolvedProvides[i].name()), this); |
| char[][] implNames = this.unresolvedProvides[i].with(); |
| TypeBinding[] impls = new TypeBinding[implNames.length]; |
| for (int j = 0; j < implNames.length; j++) |
| impls[j] = this.environment.getType(CharOperation.splitOn('.', implNames[j]), this); |
| this.implementations.put(this.services[i], impls); |
| } |
| } |
| @Override |
| public AnnotationBinding[] getAnnotations() { |
| return retrieveAnnotations(this); |
| } |
| } |