| /********************************************************************** |
| * This file is part of "Object Teams Development Tooling"-Software |
| * |
| * Copyright 2004, 2010 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 |
| * $Id: PrecedenceDeclaration.java 23401 2010-02-02 23:56:05Z stephan $ |
| * |
| * 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.ast; |
| |
| import java.util.LinkedList; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.ASTVisitor; |
| import org.eclipse.jdt.internal.compiler.ast.ASTNode; |
| import org.eclipse.jdt.internal.compiler.ast.NameReference; |
| import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; |
| import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; |
| import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| import org.eclipse.jdt.internal.compiler.lookup.BlockScope; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.Scope; |
| import org.eclipse.jdt.internal.compiler.parser.TerminalTokens; |
| import org.eclipse.objectteams.otdt.core.exceptions.InternalCompilerError; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.CallinCalloutBinding; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.PrecedenceBinding; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.RoleModel; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.TeamModel; |
| |
| |
| /** |
| * NEW for OTDT: |
| * Represents the 'precedence bindingName,..;' statement. |
| * |
| * Life cycle: |
| * + created by Parser.consumePrecedenceDeclaration() |
| * + invoked from TypeDeclaration.resolve(): |
| * - before resolving member types: resolve() |
| * - after resolving member types: merge precedence lists |
| * |
| * Further processing: |
| * creation of CallinPrecedenceAttribute |
| * semantic checking is done by PrecedenceBinding |
| * |
| * @author stephan |
| * @version $Id: PrecedenceDeclaration.java 23401 2010-02-02 23:56:05Z stephan $ |
| */ |
| public class PrecedenceDeclaration extends ASTNode { |
| |
| public boolean isAfter; |
| public NameReference[] bindingNames; |
| public int declarationSourceStart; |
| public PrecedenceBinding binding; |
| |
| public PrecedenceDeclaration(int start, int end, boolean isAfter, NameReference[] bindingNames) { |
| this.isAfter = isAfter; |
| this.bindingNames = bindingNames; |
| this.sourceStart = bindingNames[0].sourceStart; |
| this.sourceEnd = end; |
| this.declarationSourceStart = start; |
| } |
| |
| /** |
| * Resolve the name references in this precedence declaration. |
| * Push resolved declarations out to the enclosing team, if applicable. |
| * |
| * @param type enclosing type |
| */ |
| public PrecedenceBinding resolve(TypeDeclaration type) { |
| |
| names: for (int i = 0; i < this.bindingNames.length; i++) { |
| NameReference name = this.bindingNames[i]; |
| ReferenceBinding enclosing = type.binding; |
| char[] token; |
| if (name instanceof SingleNameReference) { |
| SingleNameReference sref = (SingleNameReference)name; |
| token = sref.token; |
| sref.binding = findCallinInType(type.scope, type.binding, sref.token, true/*typeAllowed*/); |
| } else { |
| assert name instanceof QualifiedNameReference; |
| QualifiedNameReference qref = (QualifiedNameReference)name; |
| for (int j = 0; j < qref.tokens.length-1; j++) { |
| char [] roleName = qref.tokens[j]; |
| enclosing = type.scope.getMemberType(roleName, enclosing); |
| if (!enclosing.isValidBinding()) { |
| type.scope.problemReporter().invalidType(name, enclosing); |
| continue names; |
| } |
| } |
| token = qref.tokens[qref.tokens.length-1]; |
| qref.binding = findCallinInType(type.scope, enclosing, token, false/*typeAllowed*/); |
| } |
| if (name.binding == null) { |
| type.scope.problemReporter().callinBindingNotFound(name, token, enclosing); |
| char[] missingName = CharOperation.concat("missingCallin:".toCharArray(), token); //$NON-NLS-1$ |
| name.binding = new CallinCalloutBinding(type.binding, missingName); |
| } else if (name.binding instanceof CallinCalloutBinding){ |
| CallinCalloutBinding callinBinding = (CallinCalloutBinding) name.binding; |
| boolean bindingIsAfter = callinBinding.callinModifier == TerminalTokens.TokenNameafter; |
| if (bindingIsAfter != this.isAfter) |
| type.scope.problemReporter().mismatchingAfterInPrecedence(name, this.isAfter); |
| } |
| } |
| checkOverriding(this.bindingNames, type.scope); |
| this.binding = new PrecedenceBinding(type.binding, this.bindingNames); |
| if ( type.enclosingType != null |
| && type.enclosingType.isTeam()) |
| { |
| type.enclosingType.addResolvedPrecedence( |
| type.binding.sourceName(), |
| this.binding); |
| } |
| return this.binding; |
| } |
| |
| /** |
| * Does list of bindings contain a pair of callins, of which one overrides the other? |
| * Signal a problem if so. |
| * |
| * @param bindingNames |
| */ |
| private static void checkOverriding(NameReference[] bindingNames, Scope scope) |
| { |
| for (int i = 1; i < bindingNames.length; i++) { |
| NameReference ref_i = bindingNames[i]; |
| if ( ref_i.binding != null |
| && ref_i.binding.kind() == Binding.BINDING) |
| { |
| for (int j = 0; j < i; j++) { |
| NameReference ref_j = bindingNames[j]; |
| if ( ref_j.binding != null |
| && ref_j.binding.kind() == Binding.BINDING) |
| { |
| CallinCalloutBinding callin_i = (CallinCalloutBinding)ref_i.binding; |
| CallinCalloutBinding callin_j = (CallinCalloutBinding)ref_j.binding; |
| if (!CharOperation.equals(callin_i.name, callin_j.name)) |
| continue; |
| ReferenceBinding role_i = callin_i._declaringRoleClass; |
| ReferenceBinding role_j = callin_j._declaringRoleClass; |
| if (role_i.isCompatibleWith(role_j)) |
| scope.problemReporter().precedenceForOverriding(ref_i, ref_j); |
| if (role_j.isCompatibleWith(role_i)) |
| scope.problemReporter().precedenceForOverriding(ref_j, ref_i); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Resolve an element of a precedence declaration. |
| * |
| * @param scope |
| * @param type |
| * @param name |
| * @return either CallinCalloutBinding or ReferenceBinding or null |
| */ |
| private Binding findCallinInType(Scope scope, |
| ReferenceBinding type, |
| char[] name, |
| boolean typeAllowed) |
| { |
| if (type.isRole()) { |
| Binding found = findCallinInRole(type, name); |
| if (found != null) |
| return found; |
| } |
| type = type.getRealClass(); |
| if (type.isTeam()) { |
| ReferenceBinding roleBinding = type.getMemberType(name); |
| if (roleBinding != null) { |
| if (!typeAllowed) { |
| scope.problemReporter().illegalDeepRoleReferenceInPrecedence(this, type, roleBinding); |
| return new ProblemReferenceBinding(name, ProblemReasons.NotVisible, roleBinding); |
| } |
| return roleBinding; |
| } |
| return null; |
| } |
| if (type.isRole()) |
| return null; // tried before, no success. |
| scope.problemReporter().illegalEnclosingForCallinName(this, type, name); |
| return null; |
| } |
| |
| /** Same as above, but now we now that type is a role. */ |
| private static CallinCalloutBinding findCallinInRole(ReferenceBinding type, char[] name) |
| { |
| assert type.isRole(); |
| RoleModel roleModel = type.roleModel; |
| type = roleModel.getClassPartBinding(); |
| ReferenceBinding current = type; |
| while (current != null && current.isRole()) |
| { |
| if (current.callinCallouts != null) { |
| for (int i = 0; i < current.callinCallouts.length; i++) { |
| CallinCalloutBinding mapping = current.callinCallouts[i]; |
| if (mapping.type == CallinCalloutBinding.CALLIN) |
| { |
| if (CharOperation.equals(name, mapping.name)) |
| return mapping; |
| } |
| } |
| } |
| current = current.superclass(); |
| } |
| ReferenceBinding[] tsupers = roleModel.getTSuperRoleBindings(); |
| for (int i = tsupers.length-1; i >= 0; i--) { // check highest prio first (which comes last in the array) |
| CallinCalloutBinding callinBinding = findCallinInRole(tsupers[i], name); |
| if (callinBinding != null) { |
| // create an unresolved binding: |
| // (details to be filled by by CallinMethodMappingsAttribute.merge->createBinding()) |
| CallinCalloutBinding result = new CallinCalloutBinding(type, name); |
| type.addCallinCallouts(new CallinCalloutBinding[]{result}); |
| return result; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Merge all precedence lists referring to the same base method. |
| * Uses the C3-algorithm for linearization. |
| * |
| * @param type enclosing type, member types have been resolved |
| * @return the (possibly reduced) array of merged lists. |
| */ |
| public static PrecedenceBinding[] mergePrecedences(TypeDeclaration type) |
| { |
| int len = (type.binding.precedences != null) ? type.binding.precedences.length : 0; |
| // TODO (SH): tsuper teams! |
| ReferenceBinding superTeam = type.binding.superclass(); |
| int lenSuper = (superTeam != null) ? superTeam.precedences.length : 0; |
| if (len+lenSuper == 0) |
| return PrecedenceBinding.NoPrecedences; |
| |
| PrecedenceBinding[] precedences = new PrecedenceBinding[len + lenSuper]; |
| for (int i = 0; i < len; i++) { |
| precedences[i] = type.binding.precedences[i]; |
| } |
| if (superTeam != null) { // redundant as per correlation with lenSuper |
| for (int i = 0; i < lenSuper; i++) { |
| precedences[len+i] = strengthenPrecendence(type, superTeam.precedences[i]); |
| } |
| } |
| int count = precedences.length; |
| for(int i=0; i<precedences.length-1; i++) { |
| if (precedences[i] == null) |
| continue; |
| for (int j = i+1; j < precedences.length; j++) { |
| if (precedences[j] == null) |
| continue; |
| if (!precedences[i].hasCommonBaseMethod(precedences[j])) |
| continue; |
| |
| LinkedList<CallinCalloutBinding> merged = new LinkedList<CallinCalloutBinding>(); |
| CallinCalloutBinding[] p1 = precedences[i].callins(false); |
| CallinCalloutBinding[] p2 = precedences[j].callins(false); |
| if (c3Merge( |
| p1, p1.length-1, |
| p2, p2.length-1, |
| merged)) |
| { |
| precedences[i] = new PrecedenceBinding(merged); |
| precedences[j] = null; |
| count--; |
| } else { |
| if (i < len) |
| type.scope.problemReporter().incompatiblePrecedenceLists(findPrecedenceSource(precedences[i], precedences[j], type), |
| type, precedences[i], precedences[j]); |
| else |
| throw new InternalCompilerError("Incompatible inherited precedence lists"); //$NON-NLS-1$ |
| } |
| } |
| } |
| if (count < precedences.length) { |
| PrecedenceBinding[] newPrecs = new PrecedenceBinding[count]; |
| int j = 0; |
| for (int i = 0; i < precedences.length; i++) { |
| if (precedences[i] != null) |
| newPrecs[j++] = precedences[i]; |
| } |
| precedences = newPrecs; |
| } |
| return precedences; |
| } |
| |
| private static ASTNode findPrecedenceSource(PrecedenceBinding prec1, PrecedenceBinding prec2, TypeDeclaration type) { |
| if (type.precedences != null) |
| for (int i = 0; i < type.precedences.length; i++) |
| if (type.precedences[i].binding == prec1 || type.precedences[i].binding == prec2) |
| return type.precedences[i]; |
| return type; |
| } |
| |
| /** |
| * Update all callin bindings from super team to current team |
| */ |
| private static PrecedenceBinding strengthenPrecendence(TypeDeclaration site, |
| PrecedenceBinding precedenceBinding) |
| { |
| CallinCalloutBinding[] superCallins = precedenceBinding.callins(false); |
| CallinCalloutBinding[] callins = new CallinCalloutBinding[superCallins.length]; |
| for (int i = 0; i < callins.length; i++) { |
| ReferenceBinding roleType = (ReferenceBinding)TeamModel |
| .strengthenRoleType(site.binding, superCallins[i]._declaringRoleClass); |
| callins[i] = findCallinInRole(roleType, superCallins[i].name); |
| } |
| return new PrecedenceBinding(site.binding, callins); |
| } |
| |
| // The algorithm from literature: |
| private static boolean c3Merge( |
| CallinCalloutBinding[] p1, int i1, |
| CallinCalloutBinding[] p2, int i2, |
| LinkedList<CallinCalloutBinding> result) |
| { |
| if (i1 < 0 && i2 < 0) // both lists empty? |
| return true; |
| if ( i2 >= 0 // have a bm? |
| && !containsCallinBinding(p2[i2], p1, i1)) // bm in {a1..an}? |
| { |
| result.addFirst(p2[i2]); |
| return c3Merge(p1, i1, p2, i2-1, result); |
| } else |
| if ( i1 >= 0 // have an an? |
| && !containsCallinBinding(p1[i1], p2, i2)) // an in {b1..bm}? |
| { |
| result.addFirst(p1[i1]); |
| return c3Merge(p1, i1-1, p2, i2, result); |
| } else |
| if (p1[i1] == p2[i2]) { |
| result.addFirst(p1[i1]); |
| return c3Merge(p1, i1-1, p2, i2-1, result); |
| } else { |
| return false; |
| } |
| } |
| |
| private static boolean containsCallinBinding(Binding binding, |
| CallinCalloutBinding[] callins, |
| int last) |
| { |
| for (int i = last; i >= 0; i--) { |
| if (callins[i] == binding) |
| return true; |
| } |
| return false; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.internal.compiler.ast.ASTNode#print(int, java.lang.StringBuffer) |
| */ |
| @Override |
| public StringBuffer print(int indent, StringBuffer output) { |
| printIndent(indent, output); |
| output.append("precedence "); //$NON-NLS-1$ |
| if (this.isAfter) |
| output.append("after "); //$NON-NLS-1$ |
| for (int i = 0; i < this.bindingNames.length; i++) { |
| this.bindingNames[i].print(indent, output); |
| output.append(i == this.bindingNames.length-1 ? ';' : ','); |
| } |
| return output; |
| } |
| |
| @Override |
| public void traverse(ASTVisitor visitor, BlockScope scope) { |
| if (visitor.visit(this, scope)) { |
| if (this.bindingNames != null) { |
| int len = this.bindingNames.length; |
| for (int i=0; i<len; i++) |
| this.bindingNames[i].traverse(visitor, scope); |
| } |
| } |
| visitor.endVisit(this, scope); |
| } |
| } |