| /********************************************************************** |
| * This file is part of "Object Teams Development Tooling"-Software |
| * |
| * Copyright 2003, 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: RoleSplitter.java 23416 2010-02-03 19:59:31Z 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.statemachine.transformer; |
| |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccAbstract; |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccFinal; |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccPrivate; |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccPublic; |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccStatic; |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccSynthetic; |
| import static org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers.AccSemicolonBody; |
| import static org.eclipse.objectteams.otdt.core.compiler.IOTConstants.OT_DELIM_LEN; |
| import static org.eclipse.objectteams.otdt.core.compiler.IOTConstants.OT_DELIM_NAME; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.codeassist.complete.CompletionOnKeyword; |
| import org.eclipse.jdt.internal.codeassist.complete.CompletionOnQualifiedTypeReference; |
| import org.eclipse.jdt.internal.codeassist.complete.CompletionOnSingleTypeReference; |
| import org.eclipse.jdt.internal.compiler.ast.ASTNode; |
| import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; |
| import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; |
| import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.TypeReference; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.lookup.BlockScope; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TagBits; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| import org.eclipse.objectteams.otdt.core.compiler.IOTConstants; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.control.ITranslationStates; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.MethodModel; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.MethodModel.ProblemDetail; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.RoleModel; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.TypeModel; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstClone; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstConverter; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstEdit; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstGenerator; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.Protections; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.TypeAnalyzer; |
| |
| |
| /** |
| * This transformer splits each role class into a class part and an interface part. |
| * |
| * Entries for STATE_ROLES_SPLIT: |
| * + createInterfacePart |
| * + transformClassPart |
| * |
| * Entry for STATE_ROLES_LINKED (directly from Dependencies.ensureRoleState()): |
| * + linkScopes |
| * + linkSuperAndBaseInIfc |
| * |
| * @author Markus Witte |
| * @version $Id: RoleSplitter.java 23416 2010-02-03 19:59:31Z stephan $ |
| */ |
| public class RoleSplitter |
| { |
| |
| /** |
| * createInterfacePart: |
| * + create a type declaration |
| * + copy field and method declarations |
| * + copy the "playedBy" relation (baseclass). |
| * Not yet connecting tsuper-interfaces (see addImplementsInterfaceReference) |
| * @param teamDeclaration enclosing team |
| * @param roleClassDeclaration role class being split. |
| */ |
| public static TypeDeclaration createInterfacePart( |
| TypeDeclaration teamDeclaration, |
| TypeDeclaration roleClassDeclaration) |
| { |
| TypeDeclaration interfaceDeclaration = AstConverter.genRoleInterface(teamDeclaration, roleClassDeclaration); |
| // rename role class before adding interface to avoid name collision: |
| renameClass(roleClassDeclaration); |
| AstEdit.addMemberTypeDeclaration(teamDeclaration, interfaceDeclaration); |
| createInterfaceStatements( |
| teamDeclaration, |
| interfaceDeclaration, |
| roleClassDeclaration); |
| interfaceDeclaration.baseclass = AstClone.copyTypeReference(roleClassDeclaration.baseclass); |
| // Note: superclass is not copied here, but in setupInterfaceForExtends, |
| // because we need to know whether superclass is a role or not |
| // (see setupInterfaceForExtends() for details.) |
| |
| if ( (roleClassDeclaration.bits & ASTNode.IsLocalType) != 0 |
| && roleClassDeclaration.scope != null) |
| { |
| ((BlockScope)roleClassDeclaration.scope.parent).addLocalType(interfaceDeclaration); |
| } |
| |
| return interfaceDeclaration; |
| } |
| |
| |
| |
| /** |
| * createInterfaceStatements |
| * add to the interface: |
| * + static final fields (move) |
| * + method declarations (copy) |
| * |
| * This methods adds declarations to the interface, which may have to be |
| * discarded later: If a role method overrides an implicitly inherited method, |
| * the interface should not repeat the method for the sake of signature weakening. |
| * CopyInheritance.weakenInterfaceSignatures does the job of cleanup. |
| */ |
| private static void createInterfaceStatements( |
| final TypeDeclaration teamDecl, |
| TypeDeclaration roleIfcDecl, |
| final TypeDeclaration roleClassDecl) |
| { |
| // Alle Methoden und Felder in memberType durchgehen und in transformiertem Zustand in das interface einf?gen |
| |
| FieldDeclaration[] fields; |
| AbstractMethodDeclaration[] methods; |
| |
| fields = roleClassDecl.fields; |
| methods = roleClassDecl.methods; |
| |
| if (fields != null) { |
| int fieldsLength = fields.length; |
| for (int i = 0; i < fieldsLength; i++) { |
| FieldDeclaration field = fields[i]; |
| // Only static final fields which are not private |
| if ( field.isStatic() |
| && (field.modifiers & AccFinal) != 0 |
| && (field.modifiers & AccPrivate) == 0) |
| { |
| // move the field: |
| AstEdit.addField(roleIfcDecl, field, false, false/*typeProblem*/, false); |
| AstEdit.removeField(roleClassDecl, field); |
| } |
| } |
| } |
| |
| if (methods != null) { |
| int methodsLength = methods.length; |
| for (int i = 0; i < methodsLength; i++) { |
| AbstractMethodDeclaration abstractMethod = methods[i]; |
| |
| // Interface can't take constructors. |
| if (abstractMethod instanceof MethodDeclaration) |
| { |
| final MethodDeclaration method = (MethodDeclaration) abstractMethod; |
| |
| // callin methods are actually copied because the wrapper calling the actual |
| // role method would otherwise be quite difficult to translate. |
| |
| if (((abstractMethod.modifiers) & AccPrivate) == 0) { |
| final MethodDeclaration newmethod = AstConverter.genRoleIfcMethod(teamDecl, method); |
| AstEdit.addMethod(roleIfcDecl, newmethod); |
| roleIfcDecl.getRoleModel()._state.addJob(ITranslationStates.STATE_ROLE_HIERARCHY_ANALYZED, // calls methods(); |
| new Runnable() { @Override |
| public void run() { |
| if ( method.binding != null |
| && (method.binding.modifiers & ClassFileConstants.AccDeprecated) != 0 |
| && newmethod.binding != null) |
| { |
| newmethod.binding.modifiers |= ClassFileConstants.AccDeprecated; |
| newmethod.binding.tagBits |= TagBits.AnnotationDeprecated; |
| } |
| }}); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * createClassPart |
| * +renameClassName Klassenname umbenennen: <class> T1.R1 -> <class> T1.__OT__R1 |
| * +renameSuperReference SuperKlasse umbenennen: <extends> T1.R1(args) -> <extends> T1.__OT__R1(args) |
| * +renameConstructors Konstruktoren umbenennen: <constructor> T1.R1(args) -> T1.__OT__R1(args) |
| * +addImplementsInterfaceReference Klasse mit Interface verbinden |
| * |
| * + strip method modifiers: make everything public |
| * (access control is via the interface). |
| */ |
| public static void transformClassPart( |
| TypeDeclaration teamDeclaration, |
| TypeDeclaration roleClassDeclaration) |
| { |
| // rename already happened during createInterfacePart. |
| char[] oldName = CharOperation.subarray(roleClassDeclaration.name, IOTConstants.OT_DELIM_LEN, -1); |
| |
| renameSuperReference(roleClassDeclaration); |
| renameConstructors(roleClassDeclaration); |
| AstEdit.addImplementsInterfaceReference(oldName, roleClassDeclaration); |
| |
| AbstractMethodDeclaration[] methods = roleClassDeclaration.methods; |
| if(methods != null) |
| { |
| for (int i=0;i<methods.length; i++) |
| { |
| // Access control is only via the interfaces. |
| // Methods invoked via an interface must be public (says JVM-spec). |
| // TODO (SH): within role methods, role type arguments are casted |
| // to the class type (weakenSignature), thereby bypassing access control! |
| if (!methods[i].isConstructor()) { |
| // modifiers of public and private methods are not changed |
| // (privates are not touched by role splitting) |
| if ( (methods[i].modifiers & (AccPublic|AccPrivate)) == 0 |
| || (methods[i].modifiers & (AccAbstract|AccStatic)) == (AccAbstract|AccStatic)) |
| { |
| MethodModel.getModel(methods[i]).storeModifiers(methods[i].modifiers); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| /* |
| * renameClass Klasse umbenennen: <class> T1.R1 -> <class> T1.__OT__R1 |
| */ |
| private static void renameClass(TypeDeclaration classTypeDeclaration) { |
| classTypeDeclaration.name = CharOperation.concat( |
| OT_DELIM_NAME, |
| classTypeDeclaration.name); |
| if (classTypeDeclaration.binding != null) |
| classTypeDeclaration.binding.sourceName = classTypeDeclaration.name; |
| } |
| |
| /* |
| * renameConstructors Konstruktoren umbenennen: <constructor> T1.R1(args) -> T1.__OT__R1(args) |
| */ |
| private static void renameConstructors(TypeDeclaration classTypeDeclaration) { |
| // ConstructorName |
| char[] newname = classTypeDeclaration.name; |
| |
| if (classTypeDeclaration.methods != null) { |
| for (int i = 0; i < classTypeDeclaration.methods.length; i++) { |
| AbstractMethodDeclaration method = classTypeDeclaration.methods[i]; |
| if (method.isConstructor()) { |
| method.selector = newname; |
| } |
| } |
| } |
| } |
| |
| /* |
| * Der Name der TypeReference muss umbenannt werden von Superclass nach __OT__Superclass |
| */ |
| private static void renameSuperReference(TypeDeclaration classTypeDeclaration){ |
| TypeReference reference = classTypeDeclaration.superclass; |
| if (reference != null) { |
| try { |
| if (reference instanceof CompletionOnKeyword) |
| return; // not a real type reference! |
| if (reference instanceof CompletionOnSingleTypeReference) |
| return; // inserting __OT__ here would break completion |
| if (reference instanceof CompletionOnQualifiedTypeReference) |
| return; // inserting __OT__ here would break completion |
| } catch (NoClassDefFoundError e) { |
| // ignore this because batch mode has no CompletionnOnKeyword. |
| } |
| if(reference instanceof SingleTypeReference){ |
| SingleTypeReference singRef = (SingleTypeReference)reference; |
| singRef.token = CharOperation.concat( |
| OT_DELIM_NAME, |
| singRef.token); |
| } else if(reference instanceof QualifiedTypeReference){ |
| QualifiedTypeReference qualRef = (QualifiedTypeReference)reference; |
| int tokenPos = qualRef.tokens.length - 1; |
| qualRef.tokens[tokenPos] = CharOperation.concat( |
| OT_DELIM_NAME, |
| qualRef.tokens[tokenPos]); |
| } |
| // At this point, we can't really reckognize whether superclass is a role, |
| // for that reason ClassScope.checkAdjustSuperclass() might have to revert |
| // some of our replacements. |
| } |
| } |
| |
| /** |
| * After bindings have been created the synthetic interface is setup |
| * to mirror the extends of the role class. |
| * This method only treats the case of regular superclasses: |
| * + copy signatures for all methods that are directly or inderictly |
| * inherited (not including java.lang.Object). |
| * (role superclasses are treated in linkSuperAndBaseInIfc()). |
| * |
| * @param teamDecl |
| * @param roleClass |
| * @param roleIfcDecl |
| */ |
| public static void setupInterfaceForExtends( |
| TypeDeclaration teamDecl, |
| TypeDeclaration roleClass, |
| TypeDeclaration roleIfcDecl) |
| { |
| ReferenceBinding superClass = roleClass.binding.superclass(); |
| if (superClass == null) |
| return; // current must be Confined |
| if ( superClass.isDirectRole() |
| && !CharOperation.equals(superClass.internalName(), IOTConstants.OTCONFINED)) |
| { |
| return; // already processed in linkSuperAndBaseInIfc() |
| } |
| ReferenceBinding[] tsupers = roleClass.getRoleModel().getTSuperRoleBindings(); |
| for (ReferenceBinding tsuperRole : tsupers) |
| if (TypeBinding.equalsEquals(tsuperRole.superclass(), superClass)) |
| return; // already included via tsuper. |
| |
| // workaround for mixed binary/source roles (cause for this situation unknown): |
| if (roleIfcDecl == null) |
| return; |
| |
| ReferenceBinding ifcBinding = roleIfcDecl.binding; |
| ReferenceBinding javaLangObject = teamDecl.scope.getJavaLangObject(); |
| |
| AstGenerator gen; |
| if (roleClass.superclass != null) |
| gen = new AstGenerator(roleClass.superclass.sourceStart, roleClass.superclass.sourceEnd); |
| else |
| gen = new AstGenerator(roleClass.sourceStart, roleClass.sourceEnd); |
| |
| while ( |
| superClass != null && |
| TypeBinding.notEquals(superClass, javaLangObject)) |
| { |
| MethodBinding[] methods = superClass.methods(); |
| methodLoop: for (int i=0; i<methods.length; i++) { |
| MethodBinding m = methods[i]; |
| if (m.isConstructor()) continue; // not inherited |
| if (m.isPrivate()) continue; // not inherited |
| if (m.isStatic()) continue; // not applicable in roles |
| if (!m.isPublic()) { // not visible via interface |
| MethodBinding existingMethod = TypeAnalyzer.findMethod(roleIfcDecl.scope, ifcBinding, m.selector, m.parameters); |
| if (existingMethod != null && existingMethod.isValidBinding()) |
| continue; |
| ProblemMethodBinding problemMethod = new ProblemMethodBinding(m, m.selector, m.parameters, ProblemReasons.NotVisible); |
| problemMethod.modifiers = m.modifiers; |
| problemMethod.modifiers |= AccAbstract|AccSemicolonBody; // don't confuse the MethodVerifier with class method in ifc |
| problemMethod.thrownExceptions = m.thrownExceptions; |
| problemMethod.returnType = m.returnType; |
| MethodModel.getModel(problemMethod).problemDetail = ProblemDetail.RoleInheritsNonPublic; |
| ifcBinding.addMethod(problemMethod); // adding binding only as to support error reporting without generating new code |
| continue; |
| } |
| |
| for (int j = 0; j < IOTConstants.OT_KEYWORDS.length; j++) { |
| if (CharOperation.equals(m.selector, IOTConstants.OT_KEYWORDS[j])) { |
| roleClass.scope.problemReporter().inheritedNameIsOTKeyword( |
| m, gen.sourceStart, gen.sourceEnd); |
| continue methodLoop; |
| } |
| } |
| MethodBinding declaredMethod = TypeAnalyzer.findCompatibleMethod(ifcBinding, m); |
| |
| if (declaredMethod == null) { |
| MethodDeclaration newmethod = AstConverter.genIfcMethodFromBinding(teamDecl, m, gen); |
| |
| // the following also creates bindings, which helps to avoid |
| // entering the same signature twice |
| // (next time findCompatibleMethod() above will also find the new method). |
| boolean wasSynthetic = false; |
| if ((newmethod.modifiers & AccSynthetic) != 0) { |
| wasSynthetic = true; |
| newmethod.modifiers &= ~AccSynthetic; |
| } |
| AstEdit.addMethod(roleIfcDecl, newmethod, wasSynthetic, false, null/*copyInheritanceSrc*/); |
| if (newmethod.binding != null) { |
| newmethod.binding.tagBits |= TagBits.ClearPrivateModifier; |
| newmethod.binding.copyInheritanceSrc = m; |
| } |
| } else { |
| if (!Protections.isAsVisible(declaredMethod.modifiers, m.modifiers)) |
| roleClass.scope.problemReporter().visibilityConflict(declaredMethod, m); |
| } |
| } |
| superClass = superClass.superclass(); |
| } |
| |
| } |
| |
| /** |
| * API for LookupEnvironment: |
| * Establish necessary links between ifc- and class-part of each role in the given unit. |
| */ |
| public static void linkRoles(CompilationUnitDeclaration unit) { |
| if (unit.types != null) |
| for (TypeDeclaration type : unit.types) |
| linkRoles(type); |
| } |
| |
| public static void linkRoles(TypeDeclaration type) { |
| if (type.isRole()) { |
| RoleModel model = type.getRoleModel(); |
| RoleSplitter.linkScopes(model); |
| RoleSplitter.linkSuperAndBaseInIfc(model); |
| } |
| if (type.memberTypes != null) |
| for (TypeDeclaration member : type.memberTypes) |
| linkRoles(member); |
| } |
| |
| /** |
| * After roles have been split and bindings have been completed, transfer |
| * type linkage from classPart to ifcPart: baseclass and superclass. |
| * In this case the superclass will be represented by a super interface. |
| * Note: tsuper roles are not yet copied. We might need to adjust types from |
| * super team to current team later (CopyInheritance.TypeLevel.adjustSuperinterfaces). |
| */ |
| private static void linkSuperAndBaseInIfc(RoleModel role) { |
| if (!role.isSourceRole()) |
| return; // local type nested in a proper role |
| ReferenceBinding binding = role.getBinding(); |
| if (binding == null || binding.isBinaryBinding()) |
| return; // no hope or already linked. |
| |
| ReferenceBinding classPart = role.getClassPartBinding(); |
| ReferenceBinding ifcPart = role.getInterfacePartBinding(); |
| |
| // baseclass: |
| if (classPart != null && ifcPart != null) { |
| ifcPart.baseclass = classPart.baseclass; |
| } |
| |
| // superclass: |
| if ( classPart != null |
| && classPart.superclass() != null) // else current must be Confined |
| { |
| ReferenceBinding superClass = classPart.superclass(); |
| if (superClass.isDirectRole()) { |
| TypeDeclaration interfaceAst = role.getInterfaceAst(); |
| if (interfaceAst != null) { |
| ifcPart = interfaceAst.binding; |
| ReferenceBinding superIfc = superClass.enclosingType().getMemberType(superClass.sourceName()); |
| superIfc = superClass.transferTypeArguments(superIfc); |
| // special: linking an ifc from a custom Confined type to "Confined" from the correct team |
| // (note that in this case the superclass is actually o.o.Team$__OT__Confined!) |
| if ( !CharOperation.equals(ifcPart.internalName(), IOTConstants.CONFINED) |
| && CharOperation.equals(superIfc.internalName(), IOTConstants.CONFINED)) |
| superIfc = ifcPart.enclosingType().getMemberType(IOTConstants.CONFINED); |
| AstEdit.addImplementsBinding(interfaceAst, superIfc); |
| } else |
| System.out.println("Binary interface part of source role: "+role); //binary binding? can we cope with that? |
| } |
| } |
| } |
| |
| |
| |
| /** |
| * After bindings and scopes are created make the interface scope |
| * inherit from the class part scope (important for nested types, |
| * which may appear in interface signatures). |
| * @param model |
| */ |
| private static void linkScopes(RoleModel model) { |
| if (model._classPart != null && model._interfacePart != null) { |
| // may already have error |
| // (we observed duplicateNestedType, nestedHidesEnclosing) |
| if (TypeModel.isIgnoreFurtherInvestigation(model._interfacePart)) |
| model._classPart.tagAsHavingErrors(); |
| else |
| model._interfacePart.scope.parent = model._classPart.scope; |
| } |
| } |
| |
| public static boolean isClassPartName(char[] typeName) { |
| return CharOperation.prefixEquals(OT_DELIM_NAME, typeName); |
| } |
| |
| public static char[] getInterfacePartName(char[] classPartName) { |
| return CharOperation.subarray(classPartName, OT_DELIM_LEN, -1); |
| } |
| } |