blob: 9d8420defae6729d9d13c571bcdc5ee92c324d16 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 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
* Fraunhofer FIRST - extended API and implementation
* Technical University Berlin - extended API and implementation
* Stephan Herrmann - Contributions for
* bug 186342 - [compiler][null] Using annotations for null checking
* bug 365519 - editorial cleanup after bug 186342 and bug 365387
* bug 365531 - [compiler][null] investigate alternative strategy for internally encoding nullness defaults
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
import java.util.ArrayList;
import java.util.function.Predicate;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment;
import org.eclipse.jdt.internal.compiler.util.HashtableOfPackage;
import org.eclipse.jdt.internal.compiler.util.HashtableOfType;
import org.eclipse.objectteams.otdt.internal.core.compiler.statemachine.transformer.RoleSplitter;
/**
* OTDT change:
*
* What: make addNotFoundType visible to sub class TeamPackageBinding
*/
public class PackageBinding extends Binding implements TypeConstants {
public long tagBits = 0; // See values in the interface TagBits below
public char[][] compoundName;
PackageBinding parent;
ArrayList<SplitPackageBinding> wrappingSplitPackageBindings;
public LookupEnvironment environment;
/** Types in this map are either uniquely visible in the current module or ProblemReferenceBindings. */
public HashtableOfType knownTypes;
/** All visible member packages, i.e. observable packages associated with modules read by the current module. */
HashtableOfPackage knownPackages;
// code representing the default that has been defined for this package (using @NonNullByDefault)
// one of Binding.{NO_NULL_DEFAULT,NULL_UNSPECIFIED_BY_DEFAULT,NONNULL_BY_DEFAULT}
private int defaultNullness = NO_NULL_DEFAULT;
public ModuleBinding enclosingModule;
/** Is this package exported from its module? NB: to query this property use {@link #isExported()} to ensure initialization. */
Boolean isExported;
protected PackageBinding() {
// for creating problem package
}
public PackageBinding(char[] topLevelPackageName, LookupEnvironment environment, ModuleBinding enclosingModule) {
this(new char[][] {topLevelPackageName}, null, environment, enclosingModule);
}
/* Create a normal package.
*/
public PackageBinding(char[][] compoundName, PackageBinding parent, LookupEnvironment environment, ModuleBinding enclosingModule) {
this.compoundName = compoundName;
this.parent = parent;
this.environment = environment;
this.knownTypes = null; // initialized if used... class counts can be very large 300-600
this.knownPackages = new HashtableOfPackage(3); // sub-package counts are typically 0-3
if (compoundName != CharOperation.NO_CHAR_CHAR)
checkIfNullAnnotationPackage();
if (enclosingModule != null)
this.enclosingModule = enclosingModule;
else if (parent != null)
this.enclosingModule = parent.enclosingModule; // stop-gap for any remaining calls that don't provide an enclosingModule (they should)
if (this.enclosingModule == null)
throw new IllegalStateException("Package should have an enclosing module"); //$NON-NLS-1$
}
public PackageBinding(LookupEnvironment environment) {
this(CharOperation.NO_CHAR_CHAR, null, environment, environment.module);
}
protected void addNotFoundPackage(char[] simpleName) {
if (!this.environment.suppressImportErrors)
this.knownPackages.put(simpleName, LookupEnvironment.TheNotFoundPackage);
}
//{ObjectTeams: ROFI changed visibility, was private
void addNotFoundType(char[] simpleName) {
// SH}
if (this.environment.suppressImportErrors)
return;
if (this.knownTypes == null)
this.knownTypes = new HashtableOfType(25);
this.knownTypes.put(simpleName, LookupEnvironment.TheNotFoundType);
}
/**
* Remembers a sub-package.
* For a split parent package this will include potentially enriching with siblings,
* in which case the enriched (split) binding will be returned.
*/
PackageBinding addPackage(PackageBinding element, ModuleBinding module) {
if ((element.tagBits & TagBits.HasMissingType) == 0) clearMissingTagBit();
this.knownPackages.put(element.compoundName[element.compoundName.length - 1], element);
return element;
}
void addType(ReferenceBinding element) {
if ((element.tagBits & TagBits.HasMissingType) == 0) clearMissingTagBit();
if (this.knownTypes == null)
this.knownTypes = new HashtableOfType(25);
char [] name = element.compoundName[element.compoundName.length - 1];
ReferenceBinding priorType = this.knownTypes.getput(name, element);
if (priorType != null && priorType.isUnresolvedType() && !element.isUnresolvedType()) {
((UnresolvedReferenceBinding) priorType).setResolvedType(element, this.environment);
}
if (this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled)
if (element.isAnnotationType() || element instanceof UnresolvedReferenceBinding) // unresolved types don't yet have the modifiers set
checkIfNullAnnotationType(element);
if (!element.isUnresolvedType() && this.wrappingSplitPackageBindings != null) {
for (SplitPackageBinding splitPackageBinding : this.wrappingSplitPackageBindings) {
if (splitPackageBinding.knownTypes != null) {
ReferenceBinding prior = splitPackageBinding.knownTypes.get(name);
if (prior != null && prior.isUnresolvedType() && !element.isUnresolvedType()) {
((UnresolvedReferenceBinding) prior).setResolvedType(element, this.environment);
splitPackageBinding.knownTypes.put(name, null); // forces re-checking for conflicts
}
}
}
}
}
ModuleBinding[] getDeclaringModules() {
return new ModuleBinding[] { this.enclosingModule };
}
void clearMissingTagBit() {
PackageBinding current = this;
do {
current.tagBits &= ~TagBits.HasMissingType;
} while ((current = current.parent) != null);
}
/*
* slash separated name
* org.eclipse.jdt.core --> org/eclipse/jdt/core
*/
@Override
public char[] computeUniqueKey(boolean isLeaf) {
return CharOperation.concatWith(this.compoundName, '/');
}
protected PackageBinding findPackage(char[] name, ModuleBinding module) {
// delegate to the module to consider the module graph:
return module.getPackage(this.compoundName, name);
}
/* Answer the subpackage named name; ask the oracle for the package if its not in the cache.
* Answer null if it could not be resolved.
*
* NOTE: This should only be used when we know there is NOT a type with the same name.
*/
PackageBinding getPackage(char[] name, ModuleBinding mod) {
PackageBinding binding = getPackage0(name);
if (binding != null) {
if (binding == LookupEnvironment.TheNotFoundPackage)
return null;
else
return binding;
}
if ((binding = findPackage(name, mod)) != null)
return binding;
// not found so remember a problem package binding in the cache for future lookups
addNotFoundPackage(name);
return null;
}
/** Answer the subpackage named name if it exists in the cache.
* Answer theNotFoundPackage if it could not be resolved the first time
* it was looked up, otherwise answer null.
* <p>
* NOTE: The returned package binding is guaranteed to be complete wrt. SplitPackageBinding,
* or, if no complete binding is yet available, we shyly answer null.
* </p><p>
* NOTE: Senders must convert theNotFoundPackage into a real problem
* package if its to returned.</p>
*/
PackageBinding getPackage0(char[] name) {
return this.knownPackages.get(name);
}
/** Variant (see {@link #getPackage0(char[])}), that may even answer an incompletely
* combined package (in the case of SplitPackageBinding).
*/
PackageBinding getPackage0Any(char[] name) {
return this.knownPackages.get(name);
}
/* Answer the type named name; ask the oracle for the type if its not in the cache.
* Answer a NotVisible problem type if the type is not visible from the invocationPackage.
* Answer null if it could not be resolved.
*
* NOTE: This should only be used by source types/scopes which know there is NOT a
* package with the same name.
*/
ReferenceBinding getType(char[] name, ModuleBinding mod) {
ReferenceBinding referenceBinding = getType0(name);
if (referenceBinding == null) {
if ((referenceBinding = this.environment.askForType(this, name, mod)) == null) {
// not found so remember a problem type binding in the cache for future lookups
addNotFoundType(name);
return null;
}
}
if (referenceBinding == LookupEnvironment.TheNotFoundType)
return null;
referenceBinding = (ReferenceBinding) BinaryTypeBinding.resolveType(referenceBinding, this.environment, false /* no raw conversion for now */);
if (referenceBinding.isNestedType())
return new ProblemReferenceBinding(new char[][]{ name }, referenceBinding, ProblemReasons.InternalNameProvided);
if (!mod.canAccess(this))
return new ProblemReferenceBinding(referenceBinding.compoundName, referenceBinding, ProblemReasons.NotAccessible);
// at this point we have only checked accessibility of the package, accessibility of the type will be checked by callers
return referenceBinding;
}
/* Answer the type named name if it exists in the cache.
* Answer theNotFoundType if it could not be resolved the first time
* it was looked up, otherwise answer null.
*
* NOTE: Senders must convert theNotFoundType into a real problem
* reference type if its to returned.
*/
ReferenceBinding getType0(char[] name) {
if (this.knownTypes == null)
return null;
return this.knownTypes.get(name);
}
/* Answer the package or type named name; ask the oracle if it is not in the cache.
* Answer null if it could not be resolved.
*
* When collisions exist between a type name & a package name, answer the type.
* Treat the package as if it does not exist... a problem was already reported when the type was defined.
*
* NOTE: no visibility checks are performed.
* THIS SHOULD ONLY BE USED BY SOURCE TYPES/SCOPES.
*/
public Binding getTypeOrPackage(char[] name, ModuleBinding mod, boolean splitPackageAllowed) {
ReferenceBinding problemBinding = null;
ReferenceBinding referenceBinding = getType0(name);
lookForType0:
if (referenceBinding != null && referenceBinding != LookupEnvironment.TheNotFoundType) {
referenceBinding = (ReferenceBinding) BinaryTypeBinding.resolveType(referenceBinding, this.environment, false /* no raw conversion for now */);
if (referenceBinding.isNestedType()) {
return new ProblemReferenceBinding(new char[][]{name}, referenceBinding, ProblemReasons.InternalNameProvided);
}
boolean isSameModule = (this instanceof SplitPackageBinding) ? referenceBinding.module() == mod : this.enclosingModule == mod;
if (!isSameModule && referenceBinding.isValidBinding() && !mod.canAccess(referenceBinding.fPackage)) {
problemBinding = new ProblemReferenceBinding(referenceBinding.compoundName, referenceBinding, ProblemReasons.NotAccessible);
break lookForType0;
}
if ((referenceBinding.tagBits & TagBits.HasMissingType) == 0) {
return referenceBinding;
}
// referenceBinding is a MissingType, will return it if no package is found
}
PackageBinding packageBinding = getPackage0(name);
if (packageBinding != null && packageBinding != LookupEnvironment.TheNotFoundPackage) {
if (!splitPackageAllowed && packageBinding instanceof SplitPackageBinding) {
return ((SplitPackageBinding) packageBinding).getVisibleFor(mod, false);
}
return packageBinding;
}
lookForType:
if (referenceBinding == null && problemBinding == null) { // have not looked for it before
if ((referenceBinding = this.environment.askForType(this, name, mod)) != null) {
if (referenceBinding.isNestedType()) {
return new ProblemReferenceBinding(new char[][]{name}, referenceBinding, ProblemReasons.InternalNameProvided);
}
if (referenceBinding.isValidBinding() && !mod.canAccess(referenceBinding.fPackage)) {
problemBinding = new ProblemReferenceBinding(referenceBinding.compoundName, referenceBinding, ProblemReasons.NotAccessible);
break lookForType;
} else {
return referenceBinding;
}
}
// Since name could not be found, add a problem binding
// to the collections so it will be reported as an error next time.
addNotFoundType(name);
}
if (packageBinding == null) { // have not looked for it before
if ((packageBinding = findPackage(name, mod)) != null) {
if (!splitPackageAllowed && packageBinding instanceof SplitPackageBinding) {
return ((SplitPackageBinding) packageBinding).getVisibleFor(mod, false);
}
return packageBinding;
}
if (referenceBinding != null && referenceBinding != LookupEnvironment.TheNotFoundType) {
if (problemBinding != null)
return problemBinding;
return referenceBinding; // found cached missing type - check if package conflict
}
addNotFoundPackage(name);
}
return problemBinding;
}
public final boolean isViewedAsDeprecated() {
if ((this.tagBits & TagBits.DeprecatedAnnotationResolved) == 0) {
this.tagBits |= TagBits.DeprecatedAnnotationResolved;
if (this.compoundName != CharOperation.NO_CHAR_CHAR) {
ReferenceBinding packageInfo = this.getType(TypeConstants.PACKAGE_INFO_NAME, this.enclosingModule);
if (packageInfo != null) {
packageInfo.initializeDeprecatedAnnotationTagBits();
this.tagBits |= packageInfo.tagBits & TagBits.AllStandardAnnotationsMask;
}
}
}
return (this.tagBits & TagBits.AnnotationDeprecated) != 0;
}
public int getDefaultNullness() {
if (this.defaultNullness == NO_NULL_DEFAULT)
return this.enclosingModule.getDefaultNullness();
return this.defaultNullness;
}
public void setDefaultNullness(int nullness) {
this.defaultNullness = nullness;
}
/**
* Find a binding (either this package or its enclosing ModuleBinding)
* where 'defaultNullness' matches the given predicate.
*/
public Binding findDefaultNullnessTarget(Predicate<Integer> predicate) {
if (predicate.test(this.defaultNullness))
return this;
if (this.defaultNullness == NO_NULL_DEFAULT)
if (predicate.test(this.enclosingModule.getDefaultNullness()))
return this.enclosingModule;
return null;
}
/* API
* Answer the receiver's binding type from Binding.BindingID.
*/
@Override
public final int kind() {
return Binding.PACKAGE;
}
@Override
public int problemId() {
if ((this.tagBits & TagBits.HasMissingType) != 0)
return ProblemReasons.NotFound;
return ProblemReasons.NoError;
}
void checkIfNullAnnotationPackage() {
LookupEnvironment env = this.environment;
if (env.globalOptions.isAnnotationBasedNullAnalysisEnabled) {
if (isPackageOfQualifiedTypeName(this.compoundName, env.getNullableAnnotationName()))
env.nullableAnnotationPackage = this;
if (isPackageOfQualifiedTypeName(this.compoundName, env.getNonNullAnnotationName()))
env.nonnullAnnotationPackage = this;
if (isPackageOfQualifiedTypeName(this.compoundName, env.getNonNullByDefaultAnnotationName()))
env.nonnullByDefaultAnnotationPackage = this;
}
}
private boolean isPackageOfQualifiedTypeName(char[][] packageName, char[][] typeName) {
int length;
if (typeName == null || (length = packageName.length) != typeName.length -1)
return false;
for (int i=0; i<length; i++)
if (!CharOperation.equals(packageName[i], typeName[i]))
return false;
return true;
}
void checkIfNullAnnotationType(ReferenceBinding type) {
// check if type is one of the configured null annotation types
// if so mark as a well known type using the corresponding typeBit:
if (this.environment.nullableAnnotationPackage == this
&& CharOperation.equals(type.compoundName, this.environment.getNullableAnnotationName())) {
type.typeBits |= TypeIds.BitNullableAnnotation;
if (!(type instanceof UnresolvedReferenceBinding)) // unresolved will need to check back for the resolved type
this.environment.nullableAnnotationPackage = null; // don't check again
} else if (this.environment.nonnullAnnotationPackage == this
&& CharOperation.equals(type.compoundName, this.environment.getNonNullAnnotationName())) {
type.typeBits |= TypeIds.BitNonNullAnnotation;
if (!(type instanceof UnresolvedReferenceBinding)) // unresolved will need to check back for the resolved type
this.environment.nonnullAnnotationPackage = null; // don't check again
} else if (this.environment.nonnullByDefaultAnnotationPackage == this
&& CharOperation.equals(type.compoundName, this.environment.getNonNullByDefaultAnnotationName())) {
type.typeBits |= TypeIds.BitNonNullByDefaultAnnotation;
if (!(type instanceof UnresolvedReferenceBinding)) // unresolved will need to check back for the resolved type
this.environment.nonnullByDefaultAnnotationPackage = null; // don't check again
} else {
type.typeBits |= this.environment.getNullAnnotationBit(type.compoundName);
}
}
@Override
public char[] readableName() /*java.lang*/ {
return CharOperation.concatWith(this.compoundName, '.');
}
@Override
public String toString() {
String str;
if (this.compoundName == CharOperation.NO_CHAR_CHAR) {
str = "The Default Package"; //$NON-NLS-1$
} else {
str = "package " + ((this.compoundName != null) ? CharOperation.toString(this.compoundName) : "UNNAMED"); //$NON-NLS-1$ //$NON-NLS-2$
}
if ((this.tagBits & TagBits.HasMissingType) != 0) {
str += "[MISSING]"; //$NON-NLS-1$
}
return str;
}
//{ObjectTeams: inline extension for accessibily's sake:
/**
* Variant of PackageBinding for handling the team package declaration of a role file.
* A team package may indeed contain nested types, which requires their names to
* be stripped and one less check during retrieval.
*/
public static class TeamPackageBinding extends PackageBinding {
/**
* @param compoundName
* @param parent
* @param environment
*/
public TeamPackageBinding(
char[][] compoundName,
PackageBinding parent,
LookupEnvironment environment,
ModuleBinding enclosingModule)
{
super(adjustTeamPackageName(compoundName), parent, environment, enclosingModule);
if (parent != null && parent.isValidBinding())
parent.addPackage(this, enclosingModule);
}
/** For nested role files the compoundName may contain '$' and '__OT__' delimitors.
* Delete '__OT__' and split at '$' into a more fine grained compound name.
* @param compoundName
* @return adjusted package name
*/
public static char[][] adjustTeamPackageName(char[][] compoundName)
{
int count = compoundName.length;
for (int i = 0; i < compoundName.length; i++)
count += CharOperation.occurencesOf('$', compoundName[i]);
char[][] result = new char[count][];
int resultIdx = 0;
for (int i = 0; i < compoundName.length; i++) {
int currentPos = 0;
while (true) {
int prevPos = currentPos;
currentPos = CharOperation.indexOf('$', compoundName[i], prevPos);
char[] element = CharOperation.subarray(compoundName[i], prevPos, currentPos); // split at '$'
if (RoleSplitter.isClassPartName(element))
element = RoleSplitter.getInterfacePartName(element); // strip '__OT__'
result[resultIdx++] = element;
if (currentPos == -1)
break;
currentPos++;
}
}
return result;
}
@Override
void addType(ReferenceBinding element) {
if (this.knownTypes == null)
this.knownTypes = new HashtableOfType(25);
char[][] elementCompoundName = CharOperation.splitOn('$', element.compoundName[element.compoundName.length - 1]);
this.knownTypes.put(elementCompoundName[elementCompoundName.length-1], element);
}
/** Similar to PackageBinding.getType(), except that we allow nested types,
* which we search in the parent package.
*/
public ReferenceBinding getRoleType(char[] name) {
ReferenceBinding binding = getType0(name);
if (binding == null) {
char[] roleName = CharOperation.concat(
CharOperation.append(this.compoundName[this.compoundName.length-1], '$'),
name);
char[][] roleCompoundName = CharOperation.arrayConcat(this.compoundName, name);
// TODO(SH): are all three attempts needed?
if ( (binding = this.environment.askForType(this.parent, roleName, this.enclosingModule)) == null
&& (binding = this.environment.askForType(this, name, this.enclosingModule)) == null
&& (binding = this.environment.askForType(roleCompoundName, this.enclosingModule)) == null)
{
// not found so remember a problem type binding in the cache for future lookups
addNotFoundType(name);
addNotFoundType(roleName);
return null;
}
}
if (binding == LookupEnvironment.TheNotFoundType)
return null;
if (binding instanceof UnresolvedReferenceBinding)
binding = ((UnresolvedReferenceBinding) binding).resolve(this.environment, false);
return binding;
}
}
// SH}
public boolean isDeclaredIn(ModuleBinding moduleBinding) {
return this.enclosingModule == moduleBinding;
}
public boolean subsumes(PackageBinding binding) {
return binding == this;
}
/**
* Is this package exported from its module?
* Does not consider export restrictions.
*/
public boolean isExported() {
if (this.isExported == null) {
if (this.enclosingModule.isAuto) {
this.isExported = Boolean.TRUE;
} else {
this.enclosingModule.getExports(); // ensure resolved and completed
if (this.isExported == null)
this.isExported = Boolean.FALSE;
}
}
return this.isExported == Boolean.TRUE;
}
/**
* If this package is uniquely visible to 'module' return a plain PackageBinding.
* In case of a conflict between a local package and foreign package
* the plain local package is returned, because this conflict will more
* appropriately be reported against the package declaration, not its references.
* In case of multiple accessible foreign packages a SplitPackageBinding is returned
* to indicate a conflict.
*/
public PackageBinding getVisibleFor(ModuleBinding module, boolean preferLocal) {
return this;
}
public boolean hasCompilationUnit(boolean checkCUs) {
if (this.knownTypes != null) {
for (ReferenceBinding knownType : this.knownTypes.valueTable) {
if (knownType != null && knownType != LookupEnvironment.TheNotFoundType && !knownType.isUnresolvedType())
return true;
}
}
if (this.environment.useModuleSystem) {
IModuleAwareNameEnvironment moduleEnv = (IModuleAwareNameEnvironment) this.environment.nameEnvironment;
return moduleEnv.hasCompilationUnit(this.compoundName, this.enclosingModule.nameForCUCheck(), checkCUs);
}
return false;
}
public void addWrappingSplitPackageBinding(SplitPackageBinding splitPackageBinding) {
if (this.wrappingSplitPackageBindings == null) {
this.wrappingSplitPackageBindings = new ArrayList<>();
}
this.wrappingSplitPackageBindings.add(splitPackageBinding);
}
}