blob: 543ce203ecf3510c1d9dbbfafec19c5f0de77857 [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2004, 2020 Fraunhofer Gesellschaft, Munich, Germany,
* for its Fraunhofer Institute for Computer Architecture and Software
* Technology (FIRST), Berlin, Germany and Technical University Berlin,
* Germany.
*
* 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
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Fraunhofer FIRST - Initial API and implementation
* Technical University Berlin - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otdt.internal.core.compiler.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions.WeavingScheme;
import org.eclipse.jdt.internal.compiler.impl.IntConstant;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodVerifier;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
import org.eclipse.objectteams.otdt.core.compiler.Pair;
import org.eclipse.objectteams.otdt.core.exceptions.InternalCompilerError;
import org.eclipse.objectteams.otdt.internal.core.compiler.ast.MethodSpec;
import org.eclipse.objectteams.otdt.internal.core.compiler.ast.RoleFileCache;
import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.AbstractAttribute;
import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.BoundClassesHierarchyAttribute;
import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.OTDynCallinBindingsAttribute;
import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.RoleBaseBindingsAttribute;
import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.WordValueAttribute;
import org.eclipse.objectteams.otdt.internal.core.compiler.control.Config;
import org.eclipse.objectteams.otdt.internal.core.compiler.lifting.LiftingEnvironment;
import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.OTClassScope;
import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.RoleTypeBinding;
import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.TThisBinding;
import org.eclipse.objectteams.otdt.internal.core.compiler.util.Protections;
import org.eclipse.objectteams.otdt.internal.core.compiler.util.RoleTypeCreator;
import org.eclipse.objectteams.otdt.internal.core.compiler.util.TSuperHelper;
import org.eclipse.objectteams.otdt.internal.core.compiler.util.TypeAnalyzer;
/**
* This class models properties of Teams.
* One instance of this class can be allocated for each ClassDeclaration
* and/or ClassBinding.
*
* Tasks:
* <ul>
* <li>Navigate to all roles.
* </ul>
*
* @author stephan
*/
public class TeamModel extends TypeModel {
// constants for bits in tagBits:
public static final int CopyRolesFromTSuperMASK = 0x0FF;
public static final int MaxTSuperRoles = 8; // so many bits allocated in CopyRolesFromTSuperMask
// details of completeTypeBindings:
public static final int BeginCopyRoles = ASTNode.Bit9;
// can lifting fail due to role abstractness?
public static final int HasAbstractRelevantRole = ASTNode.Bit10;
// did a nested team require correction of team/role flags?
public static final int HasClassKindProblem = ASTNode.Bit11;
/** The Marker interface created for this Team (_TSuper__OT__TeamName); */
public TypeDeclaration markerInterface;
private RoleBaseBindingsAttribute roleBaseBindings = null;
// pairs of base-role classes for which lifting would be ambiguous
public List<Pair<ReferenceBinding, ReferenceBinding>> ambigousLifting = new ArrayList<Pair<ReferenceBinding,ReferenceBinding>>();
/* store all role caches managed by this team. */
public FieldDeclaration[] caches = new FieldDeclaration[0];
/* the unique tthis binding for this team */
private TThisBinding tthis; // will be initialized once _binding is set.
/* a synthetic class for storing known role files */
private RoleFileCache knownRoleFiles = null;
private Map<String/*label*/,List<CallinInfo>> labelledCallinIds = null;
/** Collection of various flags. */
public int tagBits = 0;
/* While loading a role file no further role file may pass the former role in translation. */
public boolean _blockCatchup = false;
public boolean _isCopyingLateRole = false;
public LiftingEnvironment liftingEnv = null;
public TeamModel(TypeDeclaration teamAst)
{
super(teamAst);
if (teamAst.enclosingType == null && !isOrgObjectteamsTeam(teamAst))
addAttribute(WordValueAttribute.compilerVersionAttribute());
if (Config.clientIsBatchCompiler())
this.knownRoleFiles = new RoleFileCache(teamAst);
}
static boolean isOrgObjectteamsTeam(TypeDeclaration typeAst) {
if (typeAst != null && typeAst.scope != null) {
return TypeAnalyzer.isOrgObjectteamsTeam(typeAst.scope.referenceCompilationUnit());
}
return false;
}
public TeamModel(ReferenceBinding teamBinding)
{
super(teamBinding);
if (!teamBinding.isSynthInterface())
// no tthis for nested team ifc-parts
this.tthis = new TThisBinding(teamBinding);
// TODO (SH): else part should be a InternalCompilerError?
// cf. getTThis().
}
@Override
public void setBinding (ReferenceBinding binding) {
this._binding = binding;
binding.setTeamModel(this);
if (!binding.isSynthInterface())
// no tthis for nested team ifc-parts
this.tthis = new TThisBinding(binding);
}
public void addCache(FieldDeclaration cache) {
int len = this.caches.length;
System.arraycopy(
this.caches, 0,
this.caches = new FieldDeclaration[len+1], 0,
len);
this.caches[len] = cache;
}
public void addBoundClassLink(ReferenceBinding subClass, ReferenceBinding superClass) {
ensureRoleBaseBindingsAttribute();
BoundClassesHierarchyAttribute attribute = null;
for (int i = 0; i < this._attributes.length; i++) {
if (this._attributes[i].nameEquals(IOTConstants.BOUND_CLASSES_HIERARCHY)) {
attribute = (BoundClassesHierarchyAttribute)this._attributes[i];
break;
}
}
if (attribute == null)
addAttribute(attribute = new BoundClassesHierarchyAttribute());
attribute.add(subClass.attributeName(), superClass.attributeName());
}
/**
* Add a base class binding ready to write out as attribute.
* @param roleName
* @param baseName
*/
public void addRoleBaseBindingAttribute(char[] roleName, char[] baseName, boolean baseIsInterface) {
ensureRoleBaseBindingsAttribute();
this.roleBaseBindings.add(roleName, baseName, baseIsInterface);
}
private void ensureRoleBaseBindingsAttribute() {
if (this.roleBaseBindings == null) {
this.roleBaseBindings = new RoleBaseBindingsAttribute();
addAttribute(this.roleBaseBindings);
}
}
/**
* Set the given state for all contained roles.
*/
@Override
public void setMemberState(int state)
{
RoleModel[] roles = getRoles(true);
if (roles != null)
{
for (int i=0; i<roles.length; i++)
{
RoleModel role = roles[i];
role.setMemberState(state);
role.setState(state);
if ( (role.getBinding() != null)
&& (role.getBinding().isTeam()))
{
TeamModel teamModel = role.getBinding().getTeamModel();
teamModel.setState(state);
teamModel.setMemberState(state);
}
}
}
}
/**
* Set the given state for all contained roles, but not for roles-as-teams
*/
public void setMemberStateShallow(int state)
{
RoleModel[] roles = getRoles(true);
if (roles != null)
{
for (int i=0; i<roles.length; i++)
{
RoleModel role = roles[i];
role.setMemberState(state);
role.setState(state);
}
}
}
public TeamModel getSuperTeam()
{
TypeBinding superBinding = null;
if (this._ast != null)
{
if (this._ast.superclass != null)
superBinding = this._ast.superclass.resolveType(this._ast.scope);
} else {
superBinding = this._binding.superclass();
}
if (superBinding instanceof ReferenceBinding)
return ((ReferenceBinding)superBinding).getTeamModel();
return null;
}
/**
* Get all roles represented by their RoleModel.
* Considers Ast or Bindings, whatever is more appropriate.
* SH: used to ensure STATE_ROLE_INHERITANCE for this team, which is now
* moved to CalloutImplementor.getRoleModelForType() to make this
* method reusable.
* Does not take external roles into account, yet.
*
* @returns all direct roles for this Team.
*/
public RoleModel[] getRoles(boolean includeSynthInterfaces)
{
List<RoleModel> list = new LinkedList<RoleModel>();
if(this._binding != null)
{
// if binding exists, it may contain reused binary roles
// which are not present in _ast, so prefer binding.
ReferenceBinding[] roleBindings = this._binding.memberTypes();
for (int i = 0; i < roleBindings.length; i++)
{
ReferenceBinding binding = roleBindings[i];
if (binding.isEnum())
continue;
if(includeSynthInterfaces || binding.isDirectRole())
{
if (binding.roleModel != null)
list.add(binding.roleModel);
}
}
}
else
{
TypeDeclaration[] roles = this._ast.memberTypes;
if (roles != null)
{
for (int idx = 0; idx<roles.length; idx++)
{
if ((roles[idx].modifiers & ClassFileConstants.AccEnum) != 0)
continue; // enums are not roles
if (includeSynthInterfaces || roles[idx].isSourceRole())
{
RoleModel roleModel = roles[idx].getRoleModel(this);
if (roleModel != null)
list.add(roleModel);
}
}
}
}
return list.toArray(new RoleModel[list.size()]);
}
/** Answer the number of roles, binding(preferred) or ast. */
public int getNumRoles() {
if (this._binding != null)
return this._binding.memberTypes().length;
if (this._ast.memberTypes == null)
return 0;
return this._ast.memberTypes.length;
}
/** Try to re-interpret this team type as a role and answer its role model. */
public RoleModel getRoleModelOfThis() {
if (this._ast != null)
return this._ast.getRoleModel();
return this._binding.roleModel;
}
@Override
public boolean isTeam() {
return true;
}
/** This Method includes the interface part of nested teams. */
public static boolean isAnyTeam(ReferenceBinding type) {
if (type.isTeam())
return true;
if (type.isRole()) {
ReferenceBinding classPartBinding = type.roleModel.getClassPartBinding();
if (classPartBinding != null)
return classPartBinding.isTeam();
}
return false;
}
/** This Method includes the interface part of nested teams.*/
public static boolean isAnyTeam(TypeDeclaration typeDecl) {
if (typeDecl.isTeam())
return true;
if (typeDecl.isRole() && typeDecl.isInterface())
{
TypeDeclaration classPart = typeDecl.getRoleModel().getClassPartAst();
if (classPart != null)
return classPart.isTeam();
}
return false;
}
public static boolean setTagBit(ReferenceBinding teamBinding, int tagBit) {
TeamModel model = teamBinding.getTeamModel();
if ((model.tagBits & tagBit) != 0)
return false; // was already set
model.tagBits |= tagBit;
return true;
}
public static boolean hasTagBit(ReferenceBinding typeBinding, int tagBit) {
if (typeBinding._teamModel == null)
return false;
return (typeBinding._teamModel.tagBits & tagBit) != 0;
}
@Override
protected String getKindString()
{
return "Team"; //$NON-NLS-1$
}
public TThisBinding getTThis() {
if (this.tthis == null)
throw new InternalCompilerError("no tthis for "+new String(getBinding().readableName())); //$NON-NLS-1$
return this.tthis;
}
/**
* Find an enclosing type of nestedType that is a team.
* @param nestedType
* @return team or null
*/
public static ReferenceBinding getEnclosingTeam(ReferenceBinding nestedType) {
ReferenceBinding outer = nestedType.enclosingType();
while (outer != null) {
if (outer.isTeam())
return outer;
outer = outer.enclosingType();
}
return null;
}
public static TypeDeclaration getOutermostTeam(TypeDeclaration type) {
if (!type.isTeam())
return getOutermostTeam(type.enclosingType);
TypeDeclaration result = type;
while (type != null && type.isTeam()) {
result = type;
type = type.enclosingType;
}
return result;
}
private static ReferenceBinding normalizeTeam(ReferenceBinding teamBinding) {
if (teamBinding == null)
return null;
if (teamBinding.isRole() && teamBinding.isSynthInterface())
return teamBinding.roleModel.getClassPartBinding();
return teamBinding;
}
/**
* Starting a site and moving outwards find a team that contains
* roleType or a tsub-role (ie., the resulting team may be a
* subteam of roleType's enclosing team.
*
* @param site
* @param roleType
* @return a team type.
*/
public static ReferenceBinding findEnclosingTeamContainingRole(
ReferenceBinding site,
ReferenceBinding roleType)
{
site = normalizeTeam(site);
while (site != null) {
if (TeamModel.isTeamContainingRole(site, roleType))
return site;
site = site.enclosingType();
}
return null;
}
/**
* Answer whether a team contains a given role (team may be more
* specific than the role's enclosing team).
*
* @param teamBinding the team containing the role or a subteam
* @param roleBinding
* @return the answer
*/
public static boolean isTeamContainingRole (
ReferenceBinding teamBinding,
ReferenceBinding roleBinding)
{
return levelFromEnclosingTeam(teamBinding, roleBinding) != 0;
}
/**
* Try to interpret teamCandidate as an enclosing team of roleType.
*
* @param teamCandidate
* @param roleType
* @return the number of nesting levels that role type lies within teamCandidate, 0 if no match.
*/
public static int levelFromEnclosingTeam(
ReferenceBinding teamCandidate,
ReferenceBinding roleType)
{
int l = 1;
if (teamCandidate == null)
return 0;
teamCandidate = normalizeTeam(teamCandidate);
if (!teamCandidate.isTeam())
return 0;
if (!roleType.isRole())
return 0;
if (roleType.isParameterizedType())
roleType = ((ParameterizedTypeBinding)roleType).genericType();
ReferenceBinding roleOuter = (ReferenceBinding) roleType.enclosingType().erasure();
if (TypeBinding.equalsEquals(roleOuter, teamCandidate))
return 1; // shortcut
if (teamCandidate.isRole()) {
ReferenceBinding outerTeam = teamCandidate.enclosingType();
int l2 = levelFromEnclosingTeam(outerTeam, roleOuter);
if (l2 > 0) {
if (l2 == 1)
roleOuter = outerTeam.getMemberType(roleOuter.internalName());
l = l2;
}
// else nested teamCandidate might extend a non-nested team.
}
ReferenceBinding member = teamCandidate.getMemberType(roleType.internalName());
if (member == null) {
// several levels away, e.g., role nested (anonymous?):
if (roleOuter.isRole()) {
int l2 = levelFromEnclosingTeam(teamCandidate, roleOuter);
if (l2 > 0)
return l2 + 1;
}
return 0;
}
if (areTypesCompatible(teamCandidate, roleOuter))
return l;
return 0;
}
public static boolean isTeamAccessingAbstractStaticRoleMethod(ReferenceBinding type, MethodBinding method) {
int abstractStatic = ClassFileConstants.AccAbstract | ClassFileConstants.AccStatic;
if ((method.modifiers & abstractStatic) != abstractStatic)
return false;
return isTeamContainingRole(type, method.declaringClass);
}
public static boolean areTypesCompatible(ReferenceBinding type1, ReferenceBinding type2)
{
if (type1.isRole() && type2.isRole()) {
if (!type1.isInterface())
type1 = type1.roleModel.getInterfacePartBinding();
if (!type2.isInterface())
type2 = type2.roleModel.getInterfacePartBinding();
}
return type1.isCompatibleWith(type2);
}
/**
* Are (direct role?) types comparable disregarding for the moment that their team instances
* need to be compared, too?
* Role nested types are not handled here, since they are not wrapped.
*
* (Used for checking whether a cast is legal and requires instance comparison).
* @param expressionType
* @param castType
* @return the answer
*/
public static boolean isComparableToRole(ReferenceBinding expressionType, ReferenceBinding castType) {
if (!castType.isDirectRole())
return false;
return
castType.getRealType().isCompatibleWith(expressionType.getRealType())
|| expressionType.getRealType().isCompatibleWith(castType.getRealType());
}
/**
* Is left compatible to right, or are both corresponding roles
* of compatible enclosing teams (recursively).
* (Used for checking compatible refinement of a role's superclass).
*/
public static boolean areCompatibleEnclosings(ReferenceBinding left, ReferenceBinding right)
{
if (areTypesCompatible(left, right))
return true;
if (!CharOperation.equals(left.sourceName(), right.sourceName()))
return false;
ReferenceBinding leftEnclosing = left.enclosingType();
ReferenceBinding rightEnclosing = right.enclosingType();
if (leftEnclosing == null || rightEnclosing == null)
return false;
if (leftEnclosing.isTeam() && rightEnclosing.isTeam())
return areCompatibleEnclosings(leftEnclosing, rightEnclosing);
return false;
}
/**
* Get the most suitable RoleTypeBinding for roleType in a tthis context defined by scope.
* Strengthening reverses the effect of signature weakening.
*
* (Used for determining the statically known role type for lifting)
*
* @param site (guaranteed to be within the context of a team)
* @param roleType (guaranteed to be a role or an array thereof)
* @return found role - need not be a RoleTypeBinding
*/
public static TypeBinding strengthenRoleType (
ReferenceBinding site,
TypeBinding roleType)
{
ReferenceBinding enclosingTeam = site;
enclosingTeam = normalizeTeam(enclosingTeam);
if (!enclosingTeam.isTeam())
enclosingTeam = getEnclosingTeam(site);
if (enclosingTeam == null)
return roleType; // this site cannot strengthen the role type.
if (roleType.isLocalType())
return roleType;
int dimensions = roleType.dimensions();
ReferenceBinding roleRefType = (ReferenceBinding)roleType.leafComponentType();
ReferenceBinding roleEnclosing = roleRefType.enclosingType();
if (roleEnclosing.isRole() && TypeBinding.notEquals(roleEnclosing.erasure(), site.erasure())) {
// first strengthen enclosing team if it is nested:
ReferenceBinding strengthenedEnclosing = null;
if (TypeBinding.notEquals(roleEnclosing.erasure().enclosingType(), enclosingTeam.erasure().enclosingType()))
strengthenedEnclosing = (ReferenceBinding)strengthenRoleType(site, roleEnclosing);
if (strengthenedEnclosing != null && TypeBinding.notEquals(strengthenedEnclosing.erasure(), site.erasure())) {
// we indeed found a better site, so start over:
return strengthenRoleType(strengthenedEnclosing, roleType);
}
}
// check success:
if (!( roleRefType.isRole() // need a role
&& areCompatibleEnclosings(enclosingTeam, roleEnclosing))) // teams must be compatible
{
if (enclosingTeam.isRole()) // try via outer team:
return strengthenRoleType(enclosingTeam.enclosingType(), roleType);
return roleType;
}
if (roleRefType instanceof RoleTypeBinding) {
RoleTypeBinding rtb = (RoleTypeBinding)roleRefType;
if (! (rtb._teamAnchor instanceof TThisBinding))
return roleType; // don't instantiate explicit team anchor.
}
// lookup adjusted role type:
roleRefType = enclosingTeam.getMemberType(roleRefType.internalName());
if (roleRefType == null) {
if (enclosingTeam.isBinaryBinding()) {
ReferenceBinding current= enclosingTeam;
// search a role type to report against (for aborting):
while (current != null && current.isBinaryBinding())
current= current.enclosingType();
if (current != null) {
Scope scope= ((SourceTypeBinding)current).scope;
if (scope != null) {
scope.problemReporter().missingRoleInBinaryTeam(roleType.constantPoolName(), enclosingTeam);
return null;
}
}
}
if (Protections.hasClassKindProblem(enclosingTeam))
return roleType; // can't do better..
if (!enclosingTeam.isBinaryBinding()) {
Scope scope= ((SourceTypeBinding)enclosingTeam.getRealType()).scope;
scope.problemReporter().missingCopiedRole(roleType, enclosingTeam);
} else if (!site.isBinaryBinding()) {
Scope scope= ((SourceTypeBinding)site.getRealType()).scope;
scope.problemReporter().missingCopiedRole(roleType, enclosingTeam);
} else {
throw new InternalCompilerError("could not find role "+String.valueOf(roleType.constantPoolName()) //$NON-NLS-1$
+" in "+String.valueOf(site.constantPoolName()) +" and could not report regularly"); //$NON-NLS-1$ //$NON-NLS-2$
}
return roleType; // can't do better, but shouldn't reach here, because missingCopiedRole triggers AbortType.
}
VariableBinding anchor = enclosingTeam.getTeamModel().getTThis();
if (roleType.isParameterizedType()) {
// consult original role type for type arguments & type annotations:
ParameterizedTypeBinding ptb = (ParameterizedTypeBinding)roleType;
TypeBinding parameterized = ptb.environment.createParameterizedType(roleRefType, ptb.arguments, anchor, -1, roleRefType.enclosingType(), ptb.getTypeAnnotations());
if (dimensions > 0)
return ptb.environment.createArrayType(parameterized, dimensions);
return parameterized;
}
return anchor.getRoleTypeBinding(roleRefType, dimensions);
}
/**
* Starting at site look for an enclosing type that is compatible to targetEnclosing.
*/
public static ReferenceBinding strengthenEnclosing(ReferenceBinding site, ReferenceBinding targetEnclosing) {
ReferenceBinding currentEnclosing = site;
while (TypeBinding.notEquals(targetEnclosing, currentEnclosing)) {
if (currentEnclosing.isCompatibleWith(targetEnclosing)) {
return currentEnclosing;
}
currentEnclosing = currentEnclosing.enclosingType();
if (currentEnclosing == null)
throw new InternalCompilerError("Target class for callin binding not found: "+String.valueOf(targetEnclosing.readableName())); //$NON-NLS-1$
}
return targetEnclosing;
}
/**
* Find a role type from a given reference binding.
* Should only be used if no scope is available!
*
* @param site
* @param className
* @return role or null
*/
public static ReferenceBinding findMemberTypeInContext(ReferenceBinding site, char[] className) {
while (site != null) {
ReferenceBinding roleType = site.getMemberType(className);
if (roleType != null)
return roleType;
site = site.enclosingType();
}
return null;
}
public static boolean isMoreSpecificThan(ReferenceBinding type1, ReferenceBinding type2)
{
if (type1.isCompatibleWith(type2))
return true;
if (!CharOperation.equals(type1.sourceName(), type2.sourceName()))
return false;
ReferenceBinding enclosingType1 = type1.enclosingType();
ReferenceBinding enclosingType2 = type2.enclosingType();
if (enclosingType1 != null && enclosingType2 != null)
return isMoreSpecificThan(enclosingType1, enclosingType2);
return false;
}
/**
* performs role type strengthening, preforms static adjustment (OTJLD 2.3.3(a).
* Respects arrays and checks compatibility.
* @param scope client context, guaranteed to be a team or a role
* @param provided
* @param required
* @param doAdjust should the adjusted role be returned (as opposed to just the strengthened)?
* @param location where to report errors against
* @return an exact role or null
*/
public static TypeBinding getRoleToLiftTo (
Scope scope,
TypeBinding provided,
TypeBinding required,
boolean doAdjust,
ASTNode location)
{
ReferenceBinding requiredRef = null;
if ( required.isArrayType()
&& (required.leafComponentType() instanceof ReferenceBinding))
{
requiredRef = (ReferenceBinding)required.leafComponentType();
} else if (required instanceof ReferenceBinding) {
requiredRef = (ReferenceBinding)required;
}
if ( requiredRef != null
&& requiredRef.isRole())
{
requiredRef = (ReferenceBinding)strengthenRoleType(
scope.enclosingSourceType(),
requiredRef);
ReferenceBinding foundRole = null;
if (requiredRef.baseclass() == null) {
foundRole = adjustRoleToLiftTo(scope, provided, requiredRef, location);
if (foundRole != null && !doAdjust)
foundRole = requiredRef; // successful but revert to unadjusted
} else {
if (!provided.leafComponentType().isBaseType()) {
ReferenceBinding providedLeaf = (ReferenceBinding)provided.leafComponentType();
providedLeaf = RoleTypeCreator.maybeInstantiateFromPlayedBy(scope, providedLeaf);
if ( providedLeaf.isCompatibleWith(requiredRef.baseclass())
&& required.dimensions() == provided.dimensions())
{
foundRole = requiredRef;
}
}
// FIXME(SH): unneeded?
// // just check definite binding ambiguity:
// adjustRoleToLiftTo(scope, provided, requiredRef, location);
}
if (foundRole != null) {
// success by translation
if (required.dimensions() == 0)
return foundRole;
else
return scope.createArrayType(foundRole, required.dimensions());
}
}
return null;
}
/**
* Perform static adjustment according to OTJLD 2.3.3(a).
*/
private static ReferenceBinding adjustRoleToLiftTo(
Scope scope,
TypeBinding provided,
ReferenceBinding required,
ASTNode location)
{
ReferenceBinding mostGeneralFound = null;
ReferenceBinding mostSpecificFound = null;
ReferenceBinding enclosingTeam = required.enclosingType();
ReferenceBinding[] roleTypes = enclosingTeam.memberTypes();
ReferenceBinding requiredLeaf = (ReferenceBinding)required.leafComponentType();
requiredLeaf = requiredLeaf.getRealType();
for (int i = 0; i < roleTypes.length; i++) {
if (TSuperHelper.isMarkerInterface(roleTypes[i]))
continue;
RoleModel currentRole = roleTypes[i].roleModel;
if (currentRole == null) {
if (scope.referenceContext().hasErrors())
continue;
throw new InternalCompilerError("Role model of "+String.valueOf(roleTypes[i].readableName())+" is unexpectedly null"); //$NON-NLS-1$ //$NON-NLS-2$
}
ReferenceBinding currentRoleIfc = currentRole.getInterfacePartBinding();
ReferenceBinding currentBase = currentRole.getBaseTypeBinding();
if (TypeBinding.equalsEquals(mostGeneralFound, currentRoleIfc))
continue; // already seen (happens because class/ifc part show the same role)
if ( currentBase != null
&& provided.leafComponentType().isCompatibleWith(currentBase)
&& currentRoleIfc.isCompatibleWith(requiredLeaf))
{
if (mostGeneralFound == null) {
mostGeneralFound = currentRoleIfc;
mostSpecificFound = currentRoleIfc;
} else {
if (mostGeneralFound.isCompatibleWith(currentRoleIfc)) {
mostGeneralFound = currentRoleIfc; // new type is more general
} else if ( currentRoleIfc.isCompatibleWith(mostSpecificFound)) {
mostSpecificFound = currentRoleIfc;// new type is more specific
} else { // non-linear relation between different candidates.
return required; // revert to non-specific required type (additionally LFE is declared by the lift method)
}
}
}
}
return mostGeneralFound;
}
// ==== Facade for RoleFileCache: ====
/**
* Setup a structure for persistent storage of a list of know.
* role files.
* @param scope TODO
* @param environment lookup new types here.
*/
public void setupRoFiCache(Scope scope, LookupEnvironment environment) {
if (this.knownRoleFiles != null)
this.knownRoleFiles.createTypeAndBinding(scope, environment);
}
/**
* Has the role files cache been initialized properly for 'receiverType'?
*/
public static boolean hasRoFiCache(ReferenceBinding receiverType) {
if (!receiverType.isTeam())
return false;
TeamModel model = receiverType.getTeamModel();
if (model.knownRoleFiles == null)
return false;
return model.knownRoleFiles.isValid;
}
/**
* Ensure all known roles (role files) are loaded.
*/
public void readKnownRoleFiles() {
if (this.knownRoleFiles != null)
this.knownRoleFiles.readKnownRoles();
}
/**
* Generate the byte code to store the cache of known role files.
*
* @param classFile class file of the enclosing team.
*/
public void generateRoFiCache(ClassFile classFile) {
if (this.knownRoleFiles != null && !Protections.hasClassKindProblem(this._binding))
this.knownRoleFiles.generateCode(classFile);
}
/**
* Register a role file by its (relative) type name.
*
* @param name
*/
public void addKnownRoleFile(char[] name, ReferenceBinding role) {
if (this.knownRoleFiles != null)
this.knownRoleFiles.addRoleFile(name);
if (this._ast != null && (this._ast.scope instanceof OTClassScope))
((OTClassScope) this._ast.scope).recordBaseClassUse(role.baseclass); // avoid evaluating baseclass()
}
/**
* Get all known roles, those that are listed in memberTypes as well
* as those only recorded in knownRoleFiles (which might be binary
* even if the team is a SourceTypeBinding), but exclude the RoFi cache itself.
* @return intended to be a non-null array.
*/
public ReferenceBinding[] getKnownRoles() {
ReferenceBinding[] members = this._binding.memberTypes();
HashSet<String> roleNames = new HashSet<String>();
for (int i = 0; i < members.length; i++) {
if (!RoleFileCache.isRoFiCache(members[i]) && !members[i].isEnum())
roleNames.add(new String(members[i].internalName()));
}
if (this.knownRoleFiles == null) {
if (roleNames.size() == members.length) // nothing filtered?
return members;
// faster to re-iterate the array than performing the lookup below:
ReferenceBinding[] result = new ReferenceBinding[roleNames.size()];
int j=0;
for (int i = 0; i < members.length; i++) {
if (roleNames.remove(new String(members[i].internalName()))) // this also avoids duplicates in result
result[j++] = members[i];
}
return result;
}
char[][] roleFileNames = this.knownRoleFiles.getNames();
for (int i = 0; i < roleFileNames.length; i++) {
roleNames.add(new String(roleFileNames[i]));
}
int len = roleNames.size();
int i=0;
ReferenceBinding[] result = new ReferenceBinding[len];
for (Iterator<String> iter = roleNames.iterator(); iter.hasNext();) {
String name= iter.next();
result[i++] = this._binding.getMemberType(name.toCharArray());
}
return result;
}
public boolean containsRoFi(boolean ignoreConverted) {
TypeDeclaration ast = getAst();
if (ast != null && ast.memberTypes != null) {
for (TypeDeclaration role : ast.memberTypes)
if (role != null && role.isRoleFile())
if (!(ignoreConverted && role.isConverted))
return true;
}
return false;
}
public TypeBinding getMarkerInterfaceBinding(Scope scope) {
if (this.markerInterface != null)
return this.markerInterface.binding;
if ( this._binding != null
&& this._binding.superclass() != null)
{
// has not been generated yet, but perhaps this can indeed by repaired now:
TSuperHelper.addMarkerInterface(this, this._binding.superclass());
return this.markerInterface.binding;
}
PackageBinding pkgBinding = scope.compilationUnitScope().fPackage;
char[] markerName = "MissingTSuperMarker".toCharArray(); //$NON-NLS-1$
char[][] compoundName = this._binding != null ?
CharOperation.arrayConcat(this._binding.compoundName, markerName) :
new char[][] { markerName };
return scope.compilationUnitScope().environment.createMissingType(pkgBinding, compoundName);
}
//{OTDyn:
// in nested teams the outermost team assigns locally unique IDs to callins (baseMethodSpec, to be precise).
private int nextCallinID = 0;
private TeamModel getOutermostTeam() {
if (isRole())
return this._binding.enclosingType().getTeamModel().getOutermostTeam();
return this;
}
public void recordLabelledCallin(String label, MethodBinding baseMethod, int callinId, ReferenceBinding declaringRoleClass) {
List<CallinInfo> existing = getCallinsForLabel(label, declaringRoleClass);
if (existing == null) {
existing = new ArrayList<>();
this.labelledCallinIds.put(label, existing);
}
existing.add(new CallinInfo(declaringRoleClass, callinId, baseMethod));
}
/** local structure for storing information about the callin-callinId mapping. */
static class CallinInfo {
ReferenceBinding roleClass;
int callinId;
MethodBinding baseMethod;
public CallinInfo(ReferenceBinding roleClass, int callinId, MethodBinding baseMethod) {
this.roleClass = roleClass;
this.callinId = callinId;
this.baseMethod = baseMethod;
}
}
public List<CallinInfo> getCallinsForLabel(String label, ReferenceBinding declaringRoleClass) {
if (this.labelledCallinIds == null) {
this.labelledCallinIds = new HashMap<>();
TeamModel superTeam = getSuperTeam();
if (superTeam != null) {
// FIXME: Check if binary team has the (label,baseM->callinId) info.
ReferenceBinding tsuperRole = null;
ReferenceBinding currentRole = declaringRoleClass;
while (tsuperRole == null && currentRole != null && currentRole.isRole()) {
for (ReferenceBinding tsuper : currentRole.roleModel.getTSuperRoleBindings()) {
if (TypeBinding.equalsEquals(tsuper.enclosingType(), superTeam.getBinding())) {
tsuperRole = tsuper;
break;
}
}
currentRole = currentRole.superclass();
}
if (tsuperRole != null) {
List<CallinInfo> inherited = superTeam.getCallinsForLabel(label, tsuperRole);
if (inherited != null) {
this.labelledCallinIds.put(label, inherited);
int max = inherited.stream().map(p->p.callinId).max(Integer::compareTo).get();
this.nextCallinID = Math.max(this.nextCallinID, max+1);
}
}
}
}
return this.labelledCallinIds.get(label);
}
/**
* Assign a fresh callin ID for this method spec, and store it in the method spec.
* This method, however, respects overriding of named callins.
*/
public int getNewCallinId(MethodSpec baseMethodSpec, char[] label) {
TeamModel outermostTeam = getOutermostTeam();
String labelString = null;
ReferenceBinding declaringRoleClass = baseMethodSpec.declaringRoleClass;
if (label != null) {
labelString = new String(label);
List<CallinInfo> existing = outermostTeam.getCallinsForLabel(labelString, declaringRoleClass);
if (existing != null) {
LookupEnvironment environment = this._ast.scope.environment();
for (CallinInfo entry : existing) {
TypeBinding existingRole = strengthenRoleType(this._binding, entry.roleClass).original();
if (!existingRole.isCompatibleWith(declaringRoleClass)
&&!declaringRoleClass.isCompatibleWith(existingRole))
continue; // provably unrelated roles, callin cannot override
MethodBinding baseMethod = baseMethodSpec.resolvedMethod;
if (baseMethod == entry.baseMethod
|| MethodVerifier.doesMethodOverride(baseMethodSpec.resolvedMethod, entry.baseMethod, environment)) {
return entry.callinId;
}
}
}
}
int callinID = baseMethodSpec.callinID = outermostTeam.nextCallinID++;
if (labelString != null) {
outermostTeam.recordLabelledCallin(labelString, baseMethodSpec.resolvedMethod, callinID, declaringRoleClass);
}
return callinID;
}
/** Record the fact that the given callinID has been assigned in the context of this team. */
public void recordCallinId(int callinIdMax) {
TeamModel outermostTeam = getOutermostTeam();
outermostTeam.nextCallinID = Math.max(outermostTeam.nextCallinID, callinIdMax+1);
}
/** Answer the number callinIDs assigned in the context of this team. */
public int getCallinIdCount() {
return getOutermostTeam().nextCallinID;
}
public boolean hasTSuperTeamMethod(char[] selector) {
if (!isRole())
return false;
RoleModel role = getRoleModelOfThis();
for (ReferenceBinding tsuperTeam : role.getTSuperRoleBindings())
if (tsuperTeam.isTeam()) {
if (tsuperTeam.getMethods(selector) != Binding.NO_METHODS)
return true;
}
return false;
}
@Override
public void addOrMergeAttribute(AbstractAttribute attr) {
if (attr instanceof OTDynCallinBindingsAttribute) {
OTDynCallinBindingsAttribute filteredAttr = ((OTDynCallinBindingsAttribute)attr).filteredCopy(this._binding);
if (filteredAttr != null) {
super.addOrMergeAttribute(filteredAttr);
filteredAttr.createBindings(this, false);
}
recordCallinId(((OTDynCallinBindingsAttribute)attr).getCallinIdMax());
} else {
super.addOrMergeAttribute(attr);
}
}
// SH}
public boolean isAmbiguousLifting(ReferenceBinding staticRole, ReferenceBinding baseBinding) {
for (Pair<ReferenceBinding, ReferenceBinding> pair : this.ambigousLifting) {
if (TypeBinding.equalsEquals(pair.first, baseBinding) && TypeBinding.equalsEquals(pair.second, staticRole))
return true;
}
return false;
}
/**
* Can lifting to the given role potentially fail at runtime?
* @param role role to lift to.
* @return the IProblem value to be used when reporting hidden-lifting-problem against a callin binding or 0.
*/
public int canLiftingFail(ReferenceBinding role) {
if ((this.tagBits & HasAbstractRelevantRole) != 0 && role.isAbstract())
return IProblem.CallinDespiteAbstractRole;
for (Pair<ReferenceBinding, ReferenceBinding> pair : this.ambigousLifting) {
if (role.getRealClass().isCompatibleWith(pair.second) && pair.first.isCompatibleWith(role.baseclass()))
return IProblem.CallinDespiteBindingAmbiguity;
}
return 0;
}
// =======================================================================================
// = Support for ensuring uniqueness of accessIds among a team and all its super teams. =
public static interface UpdatableAccessId {
void update(int offset);
}
public static class UpdatableIntLiteral extends IntLiteral implements UpdatableAccessId {
int val;
public UpdatableIntLiteral(int val, int start, int end) {
super(String.valueOf(val).toCharArray(), null, start, end);
this.val = val;
}
@Override
public void update(int offset) {
this.constant = IntConstant.fromValue(this.val + offset);
}
}
/** Region of ids used by this team and its supers. */
int accessIdOffset = -1;
/** accessIds that may require updating. */
List<UpdatableAccessId> updatableAccessIds = null;
public void recordUpdatableAccessId(UpdatableAccessId updatableAccessId) {
if (this.updatableAccessIds == null)
this.updatableAccessIds = new ArrayList<>();
this.updatableAccessIds.add(updatableAccessId);
}
/** Update all recorded accessIds to stay clear of id ranges used by super teams. */
public int updateDecapsAccessIds() {
if (this.accessIdOffset > -1 || getWeavingScheme() == WeavingScheme.OTRE) return this.accessIdOffset;
this.accessIdOffset = 0;
TeamModel superTeam = getSuperTeam();
if (superTeam != null)
this.accessIdOffset += superTeam.updateDecapsAccessIds();
// update ASTs above super's id range if needed:
if (this.updatableAccessIds != null && this.accessIdOffset > 0)
for (UpdatableAccessId updater : this.updatableAccessIds)
updater.update(this.accessIdOffset);
if (this._specialAccess != null) {
// update attribute if needed:
this._specialAccess.accessIdOffset = this.accessIdOffset;
// include local value in the total offset:
this.accessIdOffset += this._specialAccess.nextAccessId;
}
return this.accessIdOffset;
}
}