| /******************************************************************************* |
| * Copyright (c) 2015, 2018 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.ast; |
| |
| import static org.eclipse.jdt.internal.compiler.problem.ProblemSeverities.*; |
| |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.jdt.core.compiler.CategorizedProblem; |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.core.compiler.IProblem; |
| import org.eclipse.jdt.internal.compiler.ASTVisitor; |
| import org.eclipse.jdt.internal.compiler.ClassFile; |
| import org.eclipse.jdt.internal.compiler.CompilationResult; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.impl.ReferenceContext; |
| import org.eclipse.jdt.internal.compiler.lookup.BlockScope; |
| import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope; |
| import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; |
| import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodScope; |
| import org.eclipse.jdt.internal.compiler.lookup.ModuleBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.PackageBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.Scope; |
| import org.eclipse.jdt.internal.compiler.lookup.SourceModuleBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.SplitPackageBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| import org.eclipse.jdt.internal.compiler.problem.AbortCompilation; |
| import org.eclipse.jdt.internal.compiler.problem.AbortCompilationUnit; |
| import org.eclipse.jdt.internal.compiler.problem.AbortMethod; |
| import org.eclipse.jdt.internal.compiler.problem.AbortType; |
| import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; |
| import org.eclipse.jdt.internal.compiler.util.HashtableOfObject; |
| |
| public class ModuleDeclaration extends ASTNode implements ReferenceContext { |
| |
| public ExportsStatement[] exports; |
| public RequiresStatement[] requires; |
| public UsesStatement[] uses; |
| public ProvidesStatement[] services; |
| public OpensStatement[] opens; |
| public Annotation[] annotations; |
| public int exportsCount; |
| public int requiresCount; |
| public int usesCount; |
| public int servicesCount; |
| public int opensCount; |
| public SourceModuleBinding binding; |
| public int declarationSourceStart; |
| public int declarationSourceEnd; |
| public int bodyStart; |
| public int bodyEnd; // doesn't include the trailing comment if any. |
| public int modifiersSourceStart; |
| public BlockScope scope; |
| public char[][] tokens; |
| public char[] moduleName; |
| public long[] sourcePositions; |
| public int modifiers = ClassFileConstants.AccDefault; |
| boolean ignoreFurtherInvestigation; |
| boolean hasResolvedModuleDirectives; |
| boolean hasResolvedPackageDirectives; |
| boolean hasResolvedTypeDirectives; |
| CompilationResult compilationResult; |
| |
| public ModuleDeclaration(CompilationResult compilationResult, char[][] tokens, long[] positions) { |
| this.compilationResult = compilationResult; |
| this.exportsCount = 0; |
| this.requiresCount = 0; |
| this.tokens = tokens; |
| this.moduleName = CharOperation.concatWith(tokens, '.'); |
| this.sourcePositions = positions; |
| this.sourceEnd = (int) (positions[positions.length-1] & 0x00000000FFFFFFFF); |
| this.sourceStart = (int) (positions[0] >>> 32); |
| } |
| |
| public ModuleBinding setBinding(SourceModuleBinding sourceModuleBinding) { |
| this.binding = sourceModuleBinding; |
| return sourceModuleBinding; |
| } |
| |
| public void checkAndSetModifiers() { |
| int realModifiers = this.modifiers & ExtraCompilerModifiers.AccJustFlag; |
| int expectedModifiers = ClassFileConstants.ACC_OPEN | ClassFileConstants.ACC_SYNTHETIC; |
| if ((realModifiers & ~(expectedModifiers)) != 0) { |
| this.scope.problemReporter().illegalModifierForModule(this); |
| realModifiers &= expectedModifiers; |
| } |
| int effectiveModifiers = ClassFileConstants.AccModule | realModifiers; |
| this.modifiers = this.binding.modifiers = effectiveModifiers; |
| } |
| |
| public boolean isOpen() { |
| return (this.modifiers & ClassFileConstants.ACC_OPEN) != 0; |
| } |
| |
| public void createScope(final Scope parentScope) { |
| this.scope = new MethodScope(parentScope, null, true) { |
| @Override |
| public ProblemReporter problemReporter() { |
| // this method scope has no reference context so we better deletegate to the 'real' cuScope: |
| return parentScope.problemReporter(); |
| } |
| @Override |
| public ReferenceContext referenceContext() { |
| return ModuleDeclaration.this; |
| } |
| @Override |
| public boolean isModuleScope() { |
| return true; |
| } |
| }; |
| } |
| |
| public void generateCode() { |
| if ((this.bits & ASTNode.HasBeenGenerated) != 0) |
| return; |
| this.bits |= ASTNode.HasBeenGenerated; |
| if (this.ignoreFurtherInvestigation) { |
| return; |
| } |
| try { |
| // create the result for a compiled type |
| LookupEnvironment env = this.scope.environment(); |
| ClassFile classFile = env.classFilePool.acquireForModule(this.binding, env.globalOptions); |
| classFile.initializeForModule(this.binding); |
| |
| // finalize the compiled type result |
| classFile.addModuleAttributes(this.binding, this.annotations, this.scope.referenceCompilationUnit()); |
| this.scope.referenceCompilationUnit().compilationResult.record( |
| this.binding.moduleName, |
| classFile); |
| } catch (AbortType e) { |
| if (this.binding == null) |
| return; |
| } |
| } |
| |
| /** Resolve those module directives that relate to modules (requires). */ |
| public void resolveModuleDirectives(CompilationUnitScope cuScope) { |
| if (this.binding == null) { |
| this.ignoreFurtherInvestigation = true; |
| return; |
| } |
| if (this.hasResolvedModuleDirectives) |
| return; |
| |
| this.hasResolvedModuleDirectives = true; |
| |
| Set<ModuleBinding> requiredModules = new HashSet<ModuleBinding>(); |
| Set<ModuleBinding> requiredTransitiveModules = new HashSet<ModuleBinding>(); |
| for(int i = 0; i < this.requiresCount; i++) { |
| RequiresStatement ref = this.requires[i]; |
| if (ref != null && ref.resolve(cuScope) != null) { |
| if (!requiredModules.add(ref.resolvedBinding)) { |
| cuScope.problemReporter().duplicateModuleReference(IProblem.DuplicateRequires, ref.module); |
| } |
| if (ref.isTransitive()) |
| requiredTransitiveModules.add(ref.resolvedBinding); |
| Collection<ModuleBinding> deps = ref.resolvedBinding.dependencyGraphCollector().get(); |
| if (deps.contains(this.binding)) { |
| cuScope.problemReporter().cyclicModuleDependency(this.binding, ref.module); |
| requiredModules.remove(ref.module.binding); |
| } |
| } |
| } |
| this.binding.setRequires(requiredModules.toArray(new ModuleBinding[requiredModules.size()]), |
| requiredTransitiveModules.toArray(new ModuleBinding[requiredTransitiveModules.size()])); |
| } |
| |
| /** Resolve those module directives that relate to packages (exports, opens). */ |
| public void resolvePackageDirectives(CompilationUnitScope cuScope) { |
| if (this.binding == null) { |
| this.ignoreFurtherInvestigation = true; |
| return; |
| } |
| if (this.hasResolvedPackageDirectives) |
| return; |
| |
| this.hasResolvedPackageDirectives = true; |
| |
| Set<PackageBinding> exportedPkgs = new HashSet<>(); |
| for (int i = 0; i < this.exportsCount; i++) { |
| ExportsStatement ref = this.exports[i]; |
| if (ref != null && ref.resolve(cuScope)) { |
| if (!exportedPkgs.add(ref.resolvedPackage)) { |
| cuScope.problemReporter().invalidPackageReference(IProblem.DuplicateExports, ref); |
| } |
| char[][] targets = null; |
| if (ref.targets != null) { |
| targets = new char[ref.targets.length][]; |
| for (int j = 0; j < targets.length; j++) |
| targets[j] = ref.targets[j].moduleName; |
| } |
| this.binding.addResolvedExport(ref.resolvedPackage, targets); |
| } |
| } |
| |
| HashtableOfObject openedPkgs = new HashtableOfObject(); |
| for (int i = 0; i < this.opensCount; i++) { |
| OpensStatement ref = this.opens[i]; |
| if (isOpen()) { |
| cuScope.problemReporter().invalidOpensStatement(ref, this); |
| } else { |
| if (openedPkgs.containsKey(ref.pkgName)) { |
| cuScope.problemReporter().invalidPackageReference(IProblem.DuplicateOpens, ref); |
| } else { |
| openedPkgs.put(ref.pkgName, ref); |
| ref.resolve(cuScope); |
| } |
| char[][] targets = null; |
| if (ref.targets != null) { |
| targets = new char[ref.targets.length][]; |
| for (int j = 0; j < targets.length; j++) |
| targets[j] = ref.targets[j].moduleName; |
| } |
| this.binding.addResolvedOpens(ref.resolvedPackage, targets); |
| } |
| } |
| } |
| |
| /** Resolve those module directives that relate to types (provides / uses). */ |
| public void resolveTypeDirectives(CompilationUnitScope cuScope) { |
| if (this.binding == null) { |
| this.ignoreFurtherInvestigation = true; |
| return; |
| } |
| if (this.hasResolvedTypeDirectives) |
| return; |
| |
| this.hasResolvedTypeDirectives = true; |
| ASTNode.resolveAnnotations(this.scope, this.annotations, this.binding); |
| |
| Set<TypeBinding> allTypes = new HashSet<TypeBinding>(); |
| for(int i = 0; i < this.usesCount; i++) { |
| TypeBinding serviceBinding = this.uses[i].serviceInterface.resolveType(this.scope); |
| if (serviceBinding != null && serviceBinding.isValidBinding()) { |
| if (!(serviceBinding.isClass() || serviceBinding.isInterface() || serviceBinding.isAnnotationType())) { |
| cuScope.problemReporter().invalidServiceRef(IProblem.InvalidServiceIntfType, this.uses[i].serviceInterface); |
| } |
| if (!allTypes.add(this.uses[i].serviceInterface.resolvedType)) { |
| cuScope.problemReporter().duplicateTypeReference(IProblem.DuplicateUses, this.uses[i].serviceInterface); |
| } |
| } |
| } |
| this.binding.setUses(allTypes.toArray(new TypeBinding[allTypes.size()])); |
| |
| Set<TypeBinding> interfaces = new HashSet<>(); |
| for(int i = 0; i < this.servicesCount; i++) { |
| this.services[i].resolve(this.scope); |
| TypeBinding infBinding = this.services[i].serviceInterface.resolvedType; |
| if (infBinding != null && infBinding.isValidBinding()) { |
| if (!interfaces.add(this.services[i].serviceInterface.resolvedType)) { |
| cuScope.problemReporter().duplicateTypeReference(IProblem.DuplicateServices, |
| this.services[i].serviceInterface); |
| } |
| this.binding.setImplementations(infBinding, this.services[i].getResolvedImplementations()); |
| } |
| } |
| this.binding.setServices(interfaces.toArray(new TypeBinding[interfaces.size()])); |
| } |
| |
| public void analyseCode(CompilationUnitScope skope) { |
| analyseModuleGraph(skope); |
| analyseReferencedPackages(skope); |
| } |
| |
| private void analyseReferencedPackages(CompilationUnitScope skope) { |
| if (this.exports != null) { |
| for (ExportsStatement export : this.exports) { |
| PackageBinding pb = export.resolvedPackage; |
| if (pb == null) |
| continue; |
| if (pb instanceof SplitPackageBinding) |
| pb = ((SplitPackageBinding) pb).getIncarnation(this.binding); |
| if (pb.hasCompilationUnit(true)) |
| continue; |
| skope.problemReporter().invalidPackageReference(IProblem.PackageDoesNotExistOrIsEmpty, export); |
| } |
| } |
| } |
| |
| public void analyseModuleGraph(CompilationUnitScope skope) { |
| if (this.requires != null) { |
| // collect transitively: |
| Map<String, Set<ModuleBinding>> pack2mods = new HashMap<>(); |
| for (ModuleBinding requiredModule : this.binding.getAllRequiredModules()) { |
| for (PackageBinding exportedPackage : requiredModule.getExports()) { |
| if (this.binding.canAccess(exportedPackage)) { |
| String packName = String.valueOf(exportedPackage.readableName()); |
| Set<ModuleBinding> mods = pack2mods.get(packName); |
| if (mods == null) |
| pack2mods.put(packName, mods = new HashSet<>()); |
| mods.add(requiredModule); |
| } |
| } |
| } |
| // report against the causing requires directives: |
| for (RequiresStatement requiresStat : this.requires) { |
| ModuleBinding requiredModule = requiresStat.resolvedBinding; |
| if (requiredModule != null) { |
| if (requiredModule.isDeprecated()) |
| skope.problemReporter().deprecatedModule(requiresStat.module, requiredModule); |
| analyseOneDependency(requiresStat, requiredModule, skope, pack2mods); |
| if (requiresStat.isTransitive()) { |
| for (ModuleBinding secondLevelModule : requiredModule.getAllRequiredModules()) |
| analyseOneDependency(requiresStat, secondLevelModule, skope, pack2mods); |
| } |
| } |
| } |
| } |
| } |
| |
| private void analyseOneDependency(RequiresStatement requiresStat, ModuleBinding requiredModule, CompilationUnitScope skope, |
| Map<String, Set<ModuleBinding>> pack2mods) |
| { |
| for (PackageBinding pack : requiredModule.getExports()) { |
| Set<ModuleBinding> mods = pack2mods.get(String.valueOf(pack.readableName())); |
| if (mods != null && mods.size() > 1) |
| skope.problemReporter().conflictingPackagesFromModules(pack, mods, requiresStat.sourceStart, requiresStat.sourceEnd); |
| } |
| } |
| |
| public void traverse(ASTVisitor visitor, CompilationUnitScope unitScope) { |
| visitor.visit(this, unitScope); |
| } |
| |
| public StringBuffer printHeader(int indent, StringBuffer output) { |
| if (this.annotations != null) { |
| for (int i = 0; i < this.annotations.length; i++) { |
| this.annotations[i].print(indent, output); |
| if (i != this.annotations.length - 1) |
| output.append(" "); //$NON-NLS-1$ |
| } |
| output.append('\n'); |
| } |
| if (isOpen()) { |
| output.append("open "); //$NON-NLS-1$ |
| } |
| output.append("module "); //$NON-NLS-1$ |
| output.append(CharOperation.charToString(this.moduleName)); |
| return output; |
| } |
| public StringBuffer printBody(int indent, StringBuffer output) { |
| output.append(" {"); //$NON-NLS-1$ |
| if (this.requires != null) { |
| for(int i = 0; i < this.requiresCount; i++) { |
| output.append('\n'); |
| printIndent(indent + 1, output); |
| this.requires[i].print(0, output); |
| } |
| } |
| if (this.exports != null) { |
| for(int i = 0; i < this.exportsCount; i++) { |
| output.append('\n'); |
| this.exports[i].print(indent + 1, output); |
| } |
| } |
| if (this.opens != null) { |
| for(int i = 0; i < this.opensCount; i++) { |
| output.append('\n'); |
| this.opens[i].print(indent + 1, output); |
| } |
| } |
| if (this.uses != null) { |
| for(int i = 0; i < this.usesCount; i++) { |
| output.append('\n'); |
| this.uses[i].print(indent + 1, output); |
| } |
| } |
| if (this.servicesCount != 0) { |
| for(int i = 0; i < this.servicesCount; i++) { |
| output.append('\n'); |
| this.services[i].print(indent + 1, output); |
| } |
| } |
| output.append('\n'); |
| return printIndent(indent, output).append('}'); |
| } |
| |
| @Override |
| public StringBuffer print(int indent, StringBuffer output) { |
| // |
| printIndent(indent, output); |
| printHeader(0, output); |
| return printBody(indent, output); |
| } |
| |
| @Override |
| public void abort(int abortLevel, CategorizedProblem problem) { |
| switch (abortLevel) { |
| case AbortCompilation : |
| throw new AbortCompilation(this.compilationResult, problem); |
| case AbortCompilationUnit : |
| throw new AbortCompilationUnit(this.compilationResult, problem); |
| case AbortMethod : |
| throw new AbortMethod(this.compilationResult, problem); |
| default : |
| throw new AbortType(this.compilationResult, problem); |
| } |
| } |
| |
| @Override |
| public CompilationResult compilationResult() { |
| return this.compilationResult; |
| } |
| |
| @Override |
| public CompilationUnitDeclaration getCompilationUnitDeclaration() { |
| return this.scope.referenceCompilationUnit(); |
| } |
| |
| @Override |
| public boolean hasErrors() { |
| return this.ignoreFurtherInvestigation; |
| } |
| |
| @Override |
| public void tagAsHavingErrors() { |
| this.ignoreFurtherInvestigation = true; |
| } |
| |
| @Override |
| public void tagAsHavingIgnoredMandatoryErrors(int problemId) { |
| // Nothing to do for this context; |
| } |
| |
| @Override |
| public void resetErrorFlag() { |
| this.ignoreFurtherInvestigation = false; |
| } |
| } |