blob: 6cb430b4787d84304ca15d5196f6d58e0f17c733 [file] [log] [blame]
/*******************************************************************************
* 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.CompilerOptions;
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()) {
exportedPackage = exportedPackage.getVisibleFor(requiredModule, true);
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) {
CompilerOptions compilerOptions = skope.compilerOptions();
boolean inJdtDebugCompileMode = compilerOptions.enableJdtDebugCompileMode;
if (!inJdtDebugCompileMode) {
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;
}
}