| /********************************************************************** |
| * 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. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * $Id: CalloutImplementor.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.mappings; |
| |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccAbstract; |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccNative; |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccPrivate; |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccProtected; |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccPublic; |
| import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccStatic; |
| |
| import java.util.ArrayList; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.core.compiler.IProblem; |
| import org.eclipse.jdt.internal.compiler.ast.ASTNode; |
| import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.Argument; |
| import org.eclipse.jdt.internal.compiler.ast.CastExpression; |
| import org.eclipse.jdt.internal.compiler.ast.Expression; |
| import org.eclipse.jdt.internal.compiler.ast.MessageSend; |
| import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.Statement; |
| import org.eclipse.jdt.internal.compiler.ast.ThisReference; |
| import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.WrapperKind; |
| import org.eclipse.jdt.internal.compiler.ast.Expression.DecapsulationState; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; |
| import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.Scope; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding; |
| import org.eclipse.jdt.internal.compiler.parser.TerminalTokens; |
| import org.eclipse.jdt.internal.compiler.problem.AbortCompilation; |
| import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; |
| import org.eclipse.objectteams.otdt.core.compiler.IOTConstants; |
| import org.eclipse.objectteams.otdt.core.compiler.InferenceKind; |
| import org.eclipse.objectteams.otdt.core.compiler.OTNameUtils; |
| 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.AbstractMethodMappingDeclaration; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.ast.CalloutMappingDeclaration; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.ast.FieldAccessSpec; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.ast.MethodSpec; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.ast.ParameterMapping; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.ast.PotentialLowerExpression; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.ast.PrivateRoleMethodCall; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.ast.ResultReference; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.control.Config; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.control.Dependencies; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.CallinCalloutBinding; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.CallinCalloutScope; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.DependentTypeBinding; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.ITeamAnchor; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.TThisBinding; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.WeakenedTypeBinding; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.FieldModel; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.MethodModel; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.RoleModel; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.TeamModel; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.statemachine.transformer.AbstractStatementsGenerator; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstClone; |
| 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.IProtectable; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.TSuperHelper; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.TypeAnalyzer; |
| |
| /** |
| * This Class transforms all callout-mappings (aka calloutbindings) |
| * for a given role. The callout-mappings remain in the role as AST |
| * nodes. The role-methods that match these mappings are transformed |
| * similar to the following example: |
| * void myCallout() |
| * { |
| * __OT__base.myBaseMethod(); |
| * } |
| * |
| * Special tricks: |
| * =============== |
| * What: Linking bestnames of arguments (relevant for anchored types in signatures). |
| * Why: Three signatures exist: roleMethodSpec, baseMethodSpec and wrapper, |
| * Each has its own scope (a temporary scope for baseMethodSpec) and |
| * set of locals. |
| * Where: createCallout links roleMethodSpec and wrapper |
| * getArgument links baseMethodSpec and wrapper (if not parameter mappings are given). |
| * |
| * What: Callout to private members of a role-as-base require access via two bridges |
| * Why: private methods are not exposed in the ifc-part, implicit inheritance needs |
| * redirection via the team instance, fields need accessors anyway |
| * Where: see anonymous subclass of MessageSend in transformCalloutMethodBody |
| * |
| * @author haebor |
| */ |
| public class CalloutImplementor extends MethodMappingImplementor |
| { |
| private static final int INTERFACE = 0; |
| private static final int CLASS = 1; |
| |
| private RoleModel _role; |
| |
| /** |
| * Generates a callout method for every callout mapping in the given RoleModel. |
| * @return false if errors had been reported during transformation, true else. |
| */ |
| public static boolean transformCallouts(RoleModel role) { |
| boolean success = true; |
| TypeDeclaration roleDecl = role.getAst(); |
| if (roleDecl != null && !roleDecl.isPurelyCopied && !roleDecl.binding.isSynthInterface()) { // no source level bindings present any way |
| boolean needMethodBodies = Dependencies.needMethodBodies(roleDecl) && !role.hasBaseclassProblem() && !role.isIgnoreFurtherInvestigation(); |
| // synth interfaces have no callouts anyway ;-) |
| CalloutImplementor calloutImplementor = new CalloutImplementor(role); |
| success &= calloutImplementor.transform(needMethodBodies); |
| } |
| return success; |
| } |
| |
| public CalloutImplementor(RoleModel role) |
| { |
| this._role = role; |
| this.bindingDirection = TerminalTokens.TokenNameBINDOUT; |
| } |
| |
| private boolean transform(boolean needMethodBodies) |
| { |
| AbstractMethodMappingDeclaration[] methodMappings = |
| this._role.getAst().callinCallouts; |
| |
| boolean result = true; |
| |
| if (methodMappings != null && methodMappings.length > 0) |
| { |
| for (int idx = 0; idx < methodMappings.length; idx++) |
| { |
| AbstractMethodMappingDeclaration methodMapping = methodMappings[idx]; |
| if(methodMapping.isCallout()) |
| { |
| boolean createStatements= needMethodBodies && !methodMapping.hasErrors(); |
| result &= createCallout((CalloutMappingDeclaration) methodMapping, createStatements, false/*inferred*/); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** This method drives the creation of a callout implementation for one callout mapping. */ |
| private boolean createCallout(CalloutMappingDeclaration calloutMappingDeclaration, boolean needBody, boolean isInferred) |
| { |
| CallinCalloutScope calloutScope = calloutMappingDeclaration.scope; |
| |
| calloutMappingDeclaration.updateTSuperMethods(); |
| |
| // This binding is part of the interface part of a role: |
| MethodBinding roleMethodBinding = calloutMappingDeclaration.getRoleMethod(); |
| |
| if(roleMethodBinding == null) // CLOVER: never true in jacks suite |
| { |
| // problemreporting already done in find-Base/Role-MethodBinding |
| assert(calloutMappingDeclaration.ignoreFurtherInvestigation); |
| return false; |
| } |
| if (!roleMethodBinding.isValidBinding()) { |
| if (roleMethodBinding.problemId() != ProblemReasons.NotFound) { |
| // CLOVER: never true in jacks suite |
| calloutMappingDeclaration.tagAsHavingErrors(); |
| // hopefully error has been reported! |
| return false; |
| } else { // shorthand style callout |
| // help sourceMethod() below to find another callout method |
| MethodBinding existingMethod = calloutScope.enclosingSourceType().getExactMethod(roleMethodBinding.selector, roleMethodBinding.parameters, calloutScope.compilationUnitScope()); |
| if (existingMethod != null) |
| roleMethodBinding = existingMethod; |
| } |
| } |
| |
| MethodDeclaration roleMethodDeclaration = null; |
| if (roleMethodBinding.declaringClass == calloutScope.enclosingSourceType()) |
| roleMethodDeclaration = (MethodDeclaration)roleMethodBinding.sourceMethod(); |
| |
| // have a binding but no declaration for method? -> requires creation of declaration |
| boolean foundRoleDecl = roleMethodDeclaration != null; |
| MethodBinding overriddenTSuper = null; |
| // The following code allows to use tsuper in parameter mappings |
| // (see 3.2.32-otjld-tsuper-access-1) |
| if ( foundRoleDecl |
| && roleMethodDeclaration.isCopied // foundRoleDecl => (roleMethodDeclaration != null) |
| && (roleMethodDeclaration.modifiers & AccAbstract) == 0 |
| && !TSuperHelper.isTSuper(roleMethodDeclaration.binding)) |
| { |
| // mapping conflicts with an implicitly inherited method. |
| // make the latter a tsuper version, now. |
| overriddenTSuper = roleMethodBinding; |
| // save a clone of the method binding before adding the marker arg, for use below. |
| roleMethodBinding = new MethodBinding(roleMethodBinding, roleMethodBinding.declaringClass); // clone |
| |
| TSuperHelper.addMarkerArg( |
| roleMethodDeclaration, |
| roleMethodDeclaration.binding.copyInheritanceSrc.declaringClass.enclosingType()); |
| foundRoleDecl = false; // re-create the method; |
| } |
| |
| if (!foundRoleDecl) { |
| roleMethodDeclaration = |
| createAbstractRoleMethodDeclaration(roleMethodBinding, |
| calloutMappingDeclaration); |
| if (overriddenTSuper != null) |
| roleMethodDeclaration.binding.addOverriddenTSuper(overriddenTSuper); |
| } else { |
| roleMethodDeclaration.isReusingSourceMethod = true; // foundRoleDecl => (roleMethodDeclaration != null) |
| // mark existing method as generated by the callout mapping: |
| roleMethodDeclaration.isMappingWrapper = WrapperKind.CALLOUT; |
| |
| // match locator may want to know this for the interface part, too: |
| // (SH: unsure, if this is really needed, but it doesn't hurt either ;-) |
| if (roleMethodDeclaration.interfacePartMethod != null) { |
| roleMethodDeclaration.interfacePartMethod.isReusingSourceMethod = true; |
| roleMethodDeclaration.interfacePartMethod.isMappingWrapper = WrapperKind.CALLOUT; |
| } |
| } |
| |
| if (calloutMappingDeclaration.hasSignature) |
| { |
| // Adjust arguments: |
| Argument[] args = calloutMappingDeclaration.roleMethodSpec.arguments; |
| if (args != null) |
| { |
| for (int i=0;i<args.length;i++) { |
| // if we already have a declaration and if we have signatures |
| // in the mapping declaration, use the argument names from the |
| // method mapping rather than those from the original declaration |
| // (needed for parameter mapping!). |
| if (foundRoleDecl) |
| roleMethodDeclaration.arguments[i].updateName(args[i].name); |
| |
| // also link best names of arguments of roleMethodSpec and actual wrapper |
| // ( requires wrapper argument to be bound, do this first. |
| // Note that all args must be bound in order!). |
| roleMethodDeclaration.arguments[i].bind(roleMethodDeclaration.scope, args[i].binding.type, false); |
| args[i].binding.setBestNameFromStat(roleMethodDeclaration.arguments[i]); |
| } |
| } |
| } |
| |
| if (roleMethodDeclaration != null) // try again |
| { |
| // Note: do not query the binding (as isAbstract() would do). |
| // Binding may have corrected modifiers, but take the raw modifiers. |
| if ( !roleMethodDeclaration.isCopied |
| && !roleMethodDeclaration.isGenerated |
| && (roleMethodDeclaration.modifiers & AccAbstract) == 0) |
| { |
| // bad overriding of existing / non-existant methods is handled in MethodMappingResolver already |
| roleMethodDeclaration.ignoreFurtherInvestigation = true; // don't throw "abstract method.. can only be defined by abstract class" error |
| calloutScope.problemReporter().calloutOverridesLocal( |
| this._role.getAst(), |
| calloutMappingDeclaration, |
| roleMethodDeclaration.binding); |
| return false; |
| } |
| |
| // fix flags (even if not needing body): |
| roleMethodDeclaration.isCopied = false; |
| int flagsToRemove = AccAbstract | ExtraCompilerModifiers.AccSemicolonBody | AccNative; |
| roleMethodDeclaration.modifiers &= ~flagsToRemove; |
| roleMethodDeclaration.binding.modifiers &= ~flagsToRemove; |
| roleMethodDeclaration.isGenerated = true; // even if not generated via AstGenerator. |
| if (needBody && calloutMappingDeclaration.binding.isValidBinding()) { |
| // defer generation of statements: |
| final CalloutMappingDeclaration mappingDeclaration = calloutMappingDeclaration; |
| MethodModel methodModel = MethodModel.getModel(roleMethodDeclaration); |
| if (isInferred) |
| methodModel._inferredCallout = mappingDeclaration; |
| methodModel.setStatementsGenerator( |
| new AbstractStatementsGenerator() { |
| public boolean generateStatements(AbstractMethodDeclaration methodDecl) { |
| createCalloutMethodBody((MethodDeclaration)methodDecl, mappingDeclaration); |
| return true; |
| } |
| }); |
| } else if (calloutMappingDeclaration.ignoreFurtherInvestigation) { |
| roleMethodDeclaration.binding.bytecodeMissing = true; // will not be generated, so don't complain later. |
| } |
| } |
| else // roleMethodDeclaration still null |
| { |
| // CLOVER: never reached in jacks suite ;-) |
| throw new InternalCompilerError("OT-Compiler Error: couldn't create method declaration for callout! " //$NON-NLS-1$ |
| + calloutMappingDeclaration.toString()); |
| } |
| return true; |
| } |
| |
| private MethodDeclaration createAbstractRoleMethodDeclaration( |
| MethodBinding templateBinding, |
| CalloutMappingDeclaration calloutBindingDeclaration) |
| { |
| IProtectable baseFeature = null; |
| MethodSpec baseMethodSpec = calloutBindingDeclaration.baseMethodSpec; |
| if (baseMethodSpec != null) // else syntax error? |
| baseFeature = calloutBindingDeclaration.isCalloutToField() |
| ? ((FieldAccessSpec)baseMethodSpec).resolvedField |
| : baseMethodSpec.resolvedMethod; |
| int modifiers = calloutBindingDeclaration.declaredModifiers; |
| if (modifiers == 0) { |
| // no modifiers declared in callout, look for base feature, last is the role method template |
| modifiers = ((baseFeature != null) && (templateBinding.modifiers == 0)) |
| ? baseFeature.modifiers() |
| : templateBinding.modifiers; |
| modifiers &= (ExtraCompilerModifiers.AccVisibilityMASK | AccStatic); |
| } |
| boolean isOverridingVisibility = calloutBindingDeclaration.isCalloutOverride() |
| && (calloutBindingDeclaration.declaredModifiers != 0); |
| if ( templateBinding.isValidBinding() // short-hand callout has a Problem(NotFound) here |
| && templateBinding.declaringClass.isRole()) |
| { |
| // try to find the ifc-part for 'templateBinding', |
| // if found, use its visibility modifiers for the new method declaration, |
| // because class part visibility modifiers are actually useless (always public..) |
| ReferenceBinding ifcPart = templateBinding.declaringClass.roleModel.getInterfacePartBinding(); |
| if (ifcPart != null) { |
| MethodBinding ifcMethod = TypeAnalyzer.findMethod( |
| calloutBindingDeclaration.scope, ifcPart, templateBinding.selector, templateBinding.parameters); |
| if ( ifcMethod != null && ifcMethod.isValidBinding()) |
| { |
| if (!isOverridingVisibility) { |
| // no modifiers in callout, use interface modifiers of found role method: |
| modifiers &= ~ExtraCompilerModifiers.AccVisibilityMASK; |
| modifiers |= ifcMethod.modifiers & ExtraCompilerModifiers.AccVisibilityMASK; |
| } |
| } |
| } |
| } |
| |
| // overriding an method explicitly inherited from non-role superclass? (see TPX-416) |
| boolean overridesExplicitNonRole = false; |
| ReferenceBinding superRole = null; |
| ReferenceBinding roleClass = this._role.getClassPartBinding(); |
| if (roleClass != null) |
| superRole = roleClass.superclass(); |
| if ( superRole != null // have a super class |
| && superRole.enclosingType() != roleClass.enclosingType()) // not a role from current team // (superRole != null) => (roleClass != null) |
| { |
| MethodBinding superMethod = TypeAnalyzer.findMethod( |
| calloutBindingDeclaration.scope, superRole, templateBinding.selector, templateBinding.parameters); |
| if (superMethod != null && superMethod.isValidBinding()) |
| overridesExplicitNonRole = true; // TODO(SH): need compatibility checks? (a la MethodVerifier??) |
| } |
| |
| if (calloutBindingDeclaration.binding.inferred == InferenceKind.NONE) { // don't advertise inferred callout via the interface. |
| if (templateBinding.isStatic()) // no real ifc part for static method, fake it! |
| createInterfaceFakeStatic(templateBinding, calloutBindingDeclaration); |
| else if (((modifiers & AccPrivate) == 0) && !overridesExplicitNonRole) // also no ifc part for privates and methods from explicit non-role super |
| createAbstractRoleMethodDeclarationPart(templateBinding, |
| calloutBindingDeclaration, |
| modifiers, |
| INTERFACE); |
| } |
| return createAbstractRoleMethodDeclarationPart(templateBinding, |
| calloutBindingDeclaration, |
| modifiers, |
| CLASS); |
| } |
| |
| /** In order to resolve static role methods via the interface create a method |
| * binding without a declaration. */ |
| private void createInterfaceFakeStatic(MethodBinding template, CalloutMappingDeclaration calloutDecl) |
| { |
| MethodBinding newMethod = new MethodBinding(template, this._role.getInterfacePartBinding()); |
| this._role.getInterfacePartBinding().addMethod(newMethod); |
| } |
| |
| /** |
| * Creates method in interface or class-part of _role. |
| * @param templateBinding this method is used as a template for the method that |
| * will be created. |
| * @param calloutBindingDeclaration |
| * @param part CLASS or INTERFACE |
| * @return an empty method declaration |
| */ |
| private MethodDeclaration createAbstractRoleMethodDeclarationPart( |
| MethodBinding templateBinding, |
| CalloutMappingDeclaration calloutBindingDeclaration, |
| int modifiers, |
| int part) |
| { |
| assert(templateBinding != null); |
| |
| AstGenerator gen = new AstGenerator(calloutBindingDeclaration.sourceStart, calloutBindingDeclaration.sourceEnd); |
| TypeBinding returnType; |
| if (calloutBindingDeclaration.roleMethodSpec.returnType != null) |
| // if this one is given, it might be instantiated: |
| returnType = calloutBindingDeclaration.roleMethodSpec.returnType.resolvedType; |
| else |
| // CLOVER: never reached in jacks suite |
| // this one should exist in any case: |
| returnType = calloutBindingDeclaration.roleMethodSpec.resolvedMethod.returnType; |
| MethodDeclaration newMethod = gen.method( |
| calloutBindingDeclaration.compilationResult, |
| modifiers, // start from these, adapt below. |
| returnType, |
| templateBinding.selector, |
| copyArguments(gen, |
| calloutBindingDeclaration.scope, |
| templateBinding.parameters, |
| calloutBindingDeclaration.roleMethodSpec) |
| ); |
| newMethod.typeParameters= getTypeParameters(calloutBindingDeclaration.hasSignature, |
| templateBinding, |
| calloutBindingDeclaration.roleMethodSpec, |
| gen); |
| if (templateBinding.problemId() == ProblemReasons.NotFound) { |
| // this is a short hand callout declaration: |
| MethodSpec baseMethodSpec = calloutBindingDeclaration.baseMethodSpec; |
| if (baseMethodSpec != null) { // null if missing in source code (e.g. during completion) |
| if (baseMethodSpec.isStatic()) |
| newMethod.modifiers |= AccStatic; |
| if (baseMethodSpec.resolvedMethod != null) { |
| newMethod.thrownExceptions = AstClone.copyExceptions(baseMethodSpec.resolvedMethod, gen); |
| } |
| } |
| } else { |
| newMethod.thrownExceptions = AstClone.copyExceptions(templateBinding, gen); |
| } |
| newMethod.isMappingWrapper = WrapperKind.CALLOUT; |
| |
| if (part == INTERFACE) |
| { |
| // generated callout method must also be added to the interface-part since |
| // role-splitting already happened |
| // Note: Interface part has the access modifiers! |
| newMethod.modifiers |= ExtraCompilerModifiers.AccSemicolonBody|AccAbstract; |
| AstEdit.addMethod(this._role.getInterfaceAst(), newMethod); |
| } |
| else // == CLASS |
| { |
| if ((modifiers & AccPrivate) != 0) { // don't advertize in ifc |
| // FIXME(SH): need to generate bridge methdods? |
| } else if (calloutBindingDeclaration.binding.inferred == InferenceKind.NONE) { // only if actually advertised in the ifc-part |
| // generated callout method must be public in the classPart. |
| // access control is done only via the interface part. |
| MethodModel.getModel(newMethod).storeModifiers(newMethod.modifiers); |
| newMethod.modifiers &= ~(AccProtected); |
| newMethod.modifiers |= AccPublic; |
| } |
| // abstract will be cleared once we are done. |
| AstEdit.addMethod(this._role.getAst(), newMethod); |
| } |
| calloutBindingDeclaration.updateRoleMethod(newMethod.binding); |
| return newMethod; |
| } |
| |
| /** |
| * Create the methodbody for callouts |
| */ |
| void createCalloutMethodBody( |
| MethodDeclaration roleMethodDeclaration, |
| CalloutMappingDeclaration calloutBindingDeclaration) |
| { |
| if (!transformCalloutMethodBody( |
| roleMethodDeclaration, |
| calloutBindingDeclaration)) |
| roleMethodDeclaration.tagAsHavingErrors(); |
| } |
| |
| private boolean transformCalloutMethodBody( |
| MethodDeclaration roleMethodDeclaration, |
| CalloutMappingDeclaration calloutDecl) |
| { |
| /* Type myCallout(int r) |
| * { |
| * return __OT__base.myBaseMethod(r); |
| * } |
| */ |
| |
| // NOTE (SH): Do not manually set any resolvedType fields. |
| // If this field is set, we assume resolve has completed for that element. |
| // So we rather rely on resolveType() to fill in the details later. |
| |
| // Note(SH): do not use roleMethodBinding.returnType as a short-cut: |
| // the method spec holds more specific type information (weakening!) |
| TypeBinding returnType = |
| calloutDecl.roleMethodSpec.returnType.resolvedType; |
| |
| int sStart ; |
| int sEnd; |
| |
| // the generated method implements the callout declaration, |
| // so use its source location for the wrapper method: |
| sStart = calloutDecl.sourceStart; |
| sEnd = calloutDecl.sourceEnd; |
| // these two are needed to generate correct line numbers: |
| roleMethodDeclaration.bodyStart = sStart; |
| roleMethodDeclaration.bodyEnd = sEnd; |
| if (!roleMethodDeclaration.isReusingSourceMethod) { |
| roleMethodDeclaration.sourceStart = sStart; |
| roleMethodDeclaration.sourceEnd = sEnd; |
| roleMethodDeclaration.declarationSourceStart = sStart; |
| roleMethodDeclaration.declarationSourceEnd = sEnd; |
| } // Else we have a source method in the same class. |
| // => Keep its source positions, because otherwise search et al. |
| // can't distinguish the abstract declaration and the callout |
| |
| Expression[] arguments; |
| if(calloutDecl.hasSignature) |
| { |
| arguments = makeWrapperCallArguments( |
| calloutDecl, |
| roleMethodDeclaration, |
| calloutDecl.roleMethodSpec, |
| (calloutDecl.baseMethodSpec instanceof FieldAccessSpec), |
| false /*hasResultArg*/); |
| if ( arguments == null |
| || hasParamMappingProblems(calloutDecl, returnType, roleMethodDeclaration.scope.problemReporter())) |
| { |
| return false; |
| } |
| } else { |
| arguments = makeArguments(calloutDecl, roleMethodDeclaration, calloutDecl.baseMethodSpec); |
| } |
| |
| // From this point use the source location of its base method spec, |
| // because the method body is a call to the method specific by the spec. |
| sStart = calloutDecl.baseMethodSpec.sourceStart; |
| sEnd = calloutDecl.baseMethodSpec.sourceEnd; |
| final AstGenerator gen = new AstGenerator(sStart, sEnd); |
| |
| char[] selector = calloutDecl.baseMethodSpec.selector; |
| ReferenceBinding baseType = this._role.getBaseTypeBinding(); |
| Expression receiver = gen.castExpression( |
| gen.baseNameReference(IOTConstants._OT_BASE), |
| gen.baseclassReference(baseType), |
| CastExpression.DO_WRAP); |
| |
| Expression baseAccess = null; |
| if (calloutDecl.isCalloutToField()) { |
| FieldAccessSpec fieldSpec = (FieldAccessSpec) calloutDecl.baseMethodSpec; |
| if (fieldSpec.resolvedMethod == null) { |
| // not using a decapsulation accessor but direct field access: |
| if (fieldSpec.resolvedField.isStatic()) |
| baseAccess = gen.qualifiedNameReference(fieldSpec.resolvedField); |
| else |
| baseAccess = gen.qualifiedNameReference(new char[][] {IOTConstants._OT_BASE, fieldSpec.resolvedField.name }); |
| } |
| } |
| if (baseAccess == null) { |
| if (calloutDecl.baseMethodSpec.isPrivate() && baseType.isRole()) { |
| // tricky case: callout to a private role method (base-side) |
| // requires the indirection via two wrapper methods (privateBridgeMethod) |
| |
| // compensate weakening: |
| if (baseType instanceof WeakenedTypeBinding) |
| baseType = ((WeakenedTypeBinding)baseType).getStrongType(); |
| |
| // generated message send refers to public bridge, report decapsulation now: |
| calloutDecl.scope.problemReporter().decapsulation(calloutDecl.baseMethodSpec, baseType, calloutDecl.scope); |
| |
| boolean isCalloutToField = calloutDecl.isCalloutToField(); |
| MethodBinding targetMethod = calloutDecl.baseMethodSpec.resolvedMethod; |
| baseAccess = new PrivateRoleMethodCall(receiver, selector, arguments, isCalloutToField, |
| calloutDecl.scope, baseType, targetMethod, gen); |
| } else { |
| if (calloutDecl.baseMethodSpec.isStatic() || calloutDecl.isCalloutToField()) |
| // we thought we should use an instance |
| // but callout-to-static and normal c-t-f is sent to the base *class* |
| receiver = gen.baseNameReference(baseType.getRealClass()); |
| |
| baseAccess = gen.messageSend(receiver, selector, arguments); |
| } |
| } |
| |
| boolean success = true; |
| ArrayList<Statement> statements = new ArrayList<Statement>(3); |
| if(returnType == TypeBinding.VOID) |
| { |
| // just the method call |
| statements.add(baseAccess); |
| |
| // generate empty return statement so that it gets a proper source position |
| statements.add(gen.returnStatement(null)); |
| } else { |
| if (calloutDecl.mappings == null) |
| { |
| // return the result of the method call |
| statements.add( |
| gen.returnStatement( |
| gen.potentialLift( |
| null, // use default receiver |
| baseAccess, |
| returnType, |
| false))); // no reversing required |
| } |
| else |
| { |
| // return result with parameter mapping |
| success = transformCalloutMethodBodyResultMapping( |
| statements, |
| baseAccess, |
| calloutDecl, |
| roleMethodDeclaration); |
| } |
| } |
| if (success) { |
| roleMethodDeclaration.setStatements( |
| statements.toArray(new Statement[statements.size()])); |
| } else { |
| roleMethodDeclaration.statements = new Statement[0]; |
| } |
| return success; |
| } |
| |
| /** |
| * Make the arguments of this message send. |
| * Note: it is not guaranteed that the parameters match! |
| * @param baseMethodSpec spec for the base method (must be resolved). |
| */ |
| private Expression[] makeArguments( |
| CalloutMappingDeclaration methodMapping, |
| AbstractMethodDeclaration roleMethodDecl, |
| MethodSpec baseMethodSpec) |
| { |
| assert (!methodMapping.hasSignature); |
| TypeBinding[] baseParams = baseMethodSpec.resolvedMethod.parameters; |
| Argument[] roleArgs = roleMethodDecl.arguments; |
| |
| MethodSpec roleMethodSpec = methodMapping.roleMethodSpec; |
| AstGenerator gen = new AstGenerator(roleMethodSpec.sourceStart, roleMethodSpec.sourceEnd); |
| int minArguments; |
| |
| Expression[] arguments = null; |
| int offset=0; |
| if (baseMethodSpec instanceof FieldAccessSpec) { |
| // field access is mapped to static method with additional first parameter _OT$base: |
| minArguments = baseParams.length; // exactly those needed by base. |
| arguments = new Expression[minArguments]; |
| // how many of these arguments pass a value (rather than a base target instance) |
| int valueArgCount = ((FieldAccessSpec)baseMethodSpec).isSetter() ? 1 : 0; |
| if (minArguments > valueArgCount) { // otherwise accessing a static field: no value argument |
| // TODO(SH): generalize this and the corresponding statement in |
| // makeWrapperCallArguments(). |
| // cast needed against weakened _OT$base reference |
| // and if base is a role, to go to the class type (FIXME) |
| gen.retargetFrom(baseMethodSpec); |
| arguments[0] = gen.castExpression( |
| gen.singleNameReference(IOTConstants._OT_BASE), |
| gen.baseclassReference(this._role.getBaseTypeBinding().getRealClass()), |
| this._role.getBaseTypeBinding().isRole() ? CastExpression.NEED_CLASS : CastExpression.RAW); // FIXME(SH): change to RAW and let OTRE do the cast? |
| gen.retargetFrom(roleMethodSpec); |
| minArguments--; // one is already provided. |
| offset = 1; |
| } |
| } else { |
| minArguments = Math.min(baseParams.length, (roleArgs != null) ? roleArgs.length : 0); // (minArguments > 0) => (roleArgs != null) |
| assert(minArguments == baseParams.length); |
| arguments = new Expression[minArguments]; |
| } |
| |
| for(int i=0; i<minArguments; i++) |
| { |
| arguments[i+offset] = new PotentialLowerExpression( |
| gen.singleNameReference(roleArgs[i].name), |
| adjustBaseSideType(baseParams[i+offset])); |
| } |
| return arguments; |
| } |
| |
| private boolean transformCalloutMethodBodyResultMapping( |
| ArrayList<Statement> statements, |
| Expression resultExpr, |
| CalloutMappingDeclaration calloutDecl, |
| MethodDeclaration roleMethodDeclaration) |
| { |
| Expression resultMapper = null; |
| ParameterMapping[] mappings = calloutDecl.mappings; |
| boolean resultFound = false; |
| int sStart = 0; |
| int sEnd = 0; |
| int resultStart = 0; |
| int resultEnd = 0; |
| |
| for (int i=0; i<mappings.length; i++) |
| { |
| if (!mappings[i].isUsedFor(calloutDecl.roleMethodSpec)) |
| { |
| if (CharOperation.equals(mappings[i].ident.token, IOTConstants.RESULT)) |
| { |
| if (resultFound) |
| { |
| roleMethodDeclaration.scope.problemReporter().duplicateParamMapping( |
| mappings[i], |
| IOTConstants.RESULT, |
| /*isCallout*/true); |
| return false; |
| } |
| resultMapper = mappings[i].expression; |
| sStart = mappings[i].sourceStart; |
| sEnd = mappings[i].sourceEnd; |
| resultStart = mappings[i].ident.sourceStart; |
| resultEnd = mappings[i].ident.sourceEnd; |
| resultFound = true; |
| } |
| } |
| } |
| if (!resultFound) // CLOVER: never true in jacks suite |
| { |
| roleMethodDeclaration.scope.problemReporter().unmappedParameter( |
| IOTConstants.RESULT, |
| calloutDecl.roleMethodSpec, |
| /*isCallout*/true); |
| return false; |
| } |
| assert(resultMapper != null); |
| assert (calloutDecl.baseMethodSpec.hasSignature); |
| |
| AstGenerator gen = new AstGenerator(resultStart, resultEnd); |
| |
| Statement callStatement = resultExpr; |
| |
| TypeBinding baseReturnType = calloutDecl.baseMethodSpec.returnType.resolvedType; |
| if (baseReturnType != TypeBinding.VOID) { |
| char[] localName = IOTConstants.RESULT; |
| if (calloutDecl.baseMethodSpec instanceof FieldAccessSpec) |
| localName = ((FieldAccessSpec)calloutDecl.baseMethodSpec).getFieldName(); |
| calloutDecl.resultVar = gen.localVariable( |
| localName, |
| calloutDecl.baseMethodSpec.returnType.resolvedType, |
| resultExpr); |
| calloutDecl.resultVar.type.setBaseclassDecapsulation(DecapsulationState.REPORTED); |
| callStatement = calloutDecl.resultVar; |
| } |
| |
| gen.sourceStart = sStart; |
| gen.sourceEnd = sEnd; |
| |
| statements.add(callStatement); |
| if (!roleMethodDeclaration.isStatic()) { |
| // for "base" in parameter mappings append: BaseType base = _OT$base; |
| // do this _after_ the actual forwarding so that 'base' will not shadow anything (see 3.3.21-otjld-callout-to-field-anchored-type-3) |
| FieldBinding baseField = TypeAnalyzer.findField(this._role.getBinding(), IOTConstants._OT_BASE, /*static*/false, false); |
| if (baseField != null) // CLOVER: never false in jacks suite |
| statements.add( |
| gen.localVariable(IOTConstants.BASE, |
| gen.baseclassReference(baseField.type), |
| gen.singleNameReference(IOTConstants._OT_BASE))); |
| } |
| statements.add(gen.returnStatement( |
| gen.potentialLift( |
| null, // use default receiver |
| resultMapper, |
| calloutDecl.roleMethodSpec.returnType.resolvedType, |
| false))); // no reversing required |
| return true; |
| } |
| |
| /** |
| * After building mapped arguments the parameter mapping should be empty |
| * except for a result mapping. Check if this is so. Also check for |
| * result mapping in void method. |
| * @param calloutMappingDeclaration |
| * @param returnType |
| * @param problemReporter |
| * @return the answer |
| */ |
| private static boolean hasParamMappingProblems( |
| CalloutMappingDeclaration calloutMappingDeclaration, |
| TypeBinding returnType, |
| ProblemReporter problemReporter) |
| { |
| boolean hasArgError = false; |
| ParameterMapping[] mappings = calloutMappingDeclaration.mappings; |
| if (mappings != null) { |
| for (int i = 0; i < mappings.length; i++) { |
| if (CharOperation.equals(mappings[i].ident.token, IOTConstants.RESULT)) |
| { |
| if (mappings[i].direction == TerminalTokens.TokenNameBINDOUT) |
| { |
| problemReporter.wrongBindingDirection( |
| calloutMappingDeclaration, mappings[i]); |
| hasArgError = true; |
| } |
| else if (returnType == TypeBinding.VOID) |
| { |
| problemReporter.resultMappingForVoidMethod( |
| calloutMappingDeclaration, |
| calloutMappingDeclaration.roleMethodSpec, |
| mappings[i]); |
| hasArgError = true; |
| } |
| } |
| else if (mappings[i].expression instanceof ResultReference) |
| { |
| problemReporter.mappingResultToOther( |
| calloutMappingDeclaration, mappings[i]); |
| } |
| else |
| { |
| if (!mappings[i].isUsedFor(calloutMappingDeclaration.roleMethodSpec)) |
| { |
| if (mappings[i].direction == TerminalTokens.TokenNameBINDOUT) |
| { |
| // TODO(SH): can this actually happen? Clover says: YES. |
| // would have to be an inexistent param ident? |
| problemReporter.unusedParamMap( |
| calloutMappingDeclaration, |
| mappings[i]); |
| // warning only |
| } |
| else |
| { |
| problemReporter.wrongBindingDirection( |
| calloutMappingDeclaration, mappings[i]); |
| hasArgError = true; |
| } |
| } |
| } |
| } |
| } |
| return hasArgError; |
| } |
| // ======================================================= |
| |
| /** |
| * Note that as a side effect, this method modifies methodMapping.mappings! |
| * @param methodMapping lookup method spec and parameter mapping here |
| * @param wrapperDeclaration use these if no mapping is involved |
| * @param implParameters parameters of the implemented method to invoke |
| * @param idx argument position on the target side |
| * @param hasResultArgument (ignored) |
| * @param sourceMethodSpec this signature defines the provided args |
| * @return a mapped argument expression or null |
| */ |
| Expression getArgument( |
| AbstractMethodMappingDeclaration methodMapping, |
| MethodDeclaration wrapperDeclaration, |
| TypeBinding[] implParameters, |
| int idx, |
| boolean hasResultArgument, |
| MethodSpec sourceMethodSpec) |
| { |
| MethodSpec implementationMethodSpec = methodMapping.getImplementationMethodSpec(); |
| Argument[] specifiedArgs = implementationMethodSpec.arguments; |
| |
| ParameterMapping[] parameterMappings = methodMapping.mappings; |
| Expression mappedArgExpr = null; |
| char[] targetArgName = null; |
| if (parameterMappings == null) |
| { |
| targetArgName = wrapperDeclaration.arguments[idx].name; |
| mappedArgExpr = genSimpleArgExpr( |
| targetArgName, |
| ((CalloutMappingDeclaration)methodMapping).baseMethodSpec); |
| // if parameters are not mapped, identify provided and expected args. |
| // Note that expected args means those of the baseMethodSpec |
| // which are otherwise unreachable. |
| // (stored in a temporary scope, see AbstractMethodMappingDeclaration.resolveMethodSpecs) |
| if (idx < specifiedArgs.length) // CLOVER: never false in jacks suite |
| specifiedArgs[idx].binding.setBestNameFromStat(wrapperDeclaration.arguments[idx]); |
| } |
| else |
| { |
| if (methodMapping.mappingExpressions == null) { |
| assert !methodMapping.hasParsedParamMappings : "expect lack of parsing as cause for missing expressions"; //$NON-NLS-1$ |
| return null; // this indicates an error (required param mappings have not been parsed) |
| } |
| targetArgName = implementationMethodSpec.arguments[idx].name; |
| Pair<Expression, Integer> mapper = methodMapping.mappingExpressions[idx]; |
| mappedArgExpr = mapper.first; |
| } |
| if (mappedArgExpr != null) { |
| if (idx >= implParameters.length) // CLOVER: never true in jacks suite |
| return mappedArgExpr; // arg is invisible to receiver, don't lower |
| TypeBinding expectedType = implParameters[idx]; |
| |
| // if type is anchored to another arg of the same binding, we have to translate: |
| // 1.) is it a param-anchored type? |
| if (expectedType.leafComponentType() instanceof DependentTypeBinding) { |
| DependentTypeBinding dependentExpectedLeaf = (DependentTypeBinding)expectedType.leafComponentType(); |
| int anchorArgPos = dependentExpectedLeaf._argumentPosition; |
| if (anchorArgPos != -1) { |
| // 2.) need to translate the position using the param mapping? |
| if (methodMapping.positions != null) |
| anchorArgPos = methodMapping.positions[anchorArgPos]-1; |
| // 3.) with this position retrieve the source argument and re-anchor the type to this anchor: |
| ITeamAnchor mappedAnchor = sourceMethodSpec.arguments[anchorArgPos].binding; |
| expectedType = mappedAnchor.getRoleTypeBinding(dependentExpectedLeaf, expectedType.dimensions()); |
| } |
| } |
| |
| return new PotentialLowerExpression(mappedArgExpr, adjustBaseSideType(expectedType)); |
| } |
| |
| wrapperDeclaration.scope.problemReporter() |
| .unmappedParameter( |
| targetArgName, |
| implementationMethodSpec, |
| methodMapping.isCallout()); |
| return null; |
| } |
| |
| TypeBinding adjustBaseSideType(TypeBinding givenType) { |
| ReferenceBinding baseBinding = this._role.getBaseTypeBinding(); |
| if (baseBinding == null) return givenType; |
| if (givenType.leafComponentType().isBaseType()) return givenType; |
| ReferenceBinding givenLeaf = (ReferenceBinding)givenType.leafComponentType(); |
| int dimensions = givenType.dimensions(); |
| TypeVariableBinding[] arguments = null; |
| if (givenType.isParameterizedType()) |
| arguments = ((ParameterizedTypeBinding)givenType).typeVariables(); |
| if (DependentTypeBinding.isDependentType(baseBinding)) { |
| ITeamAnchor anchor = ((DependentTypeBinding)baseBinding).getAnchor(); |
| if (anchor.isTeamContainingRole(givenLeaf)) { |
| if (anchor.getResolvedType() != givenLeaf.enclosingType()) |
| givenLeaf = (ReferenceBinding) TeamModel.strengthenRoleType((ReferenceBinding) anchor.getResolvedType(), givenLeaf); |
| return anchor.getDependentTypeBinding(givenLeaf, -1, arguments, dimensions); |
| } |
| } |
| return givenType; |
| } |
| |
| @Override |
| TypeBinding[] getImplementationParamters(AbstractMethodMappingDeclaration methodMapping, MethodDeclaration wrapperMethod) |
| { |
| TypeBinding[] result= super.getImplementationParamters(methodMapping, wrapperMethod); |
| // if we have type variables replace them with the version of the wrapper method: |
| int l= result.length; |
| if (result == Binding.NO_PARAMETERS || l == 0) |
| return result; |
| System.arraycopy(result, 0, result= new TypeBinding[l], 0, l); |
| TypeVariableBinding[] variables= wrapperMethod.binding.typeVariables(); |
| for (int i = 0; i < result.length; i++) |
| result[i] = substituteVariables(result[i], variables); |
| return result; |
| } |
| |
| /** Given a binding of a callout mapping as inherited from a super interface, |
| * create the wrapper method for the current role class. |
| */ |
| public void generateFromBinding(CallinCalloutBinding mapping) |
| { |
| TypeDeclaration roleClass = this._role.getClassPartAst(); |
| boolean needBody = Dependencies.needMethodBodies(roleClass); |
| AstGenerator gen = new AstGenerator(roleClass.sourceStart, roleClass.sourceEnd); |
| CalloutMappingDeclaration callout = gen.calloutMappingDeclaration(roleClass.compilationResult); |
| callout.roleMethodSpec = fromMethodBinding(mapping._roleMethodBinding, gen); |
| if (mapping._baseMethods.length > 0) |
| callout.baseMethodSpec = fromMethodBinding(mapping._baseMethods[0], gen); |
| else |
| assert !needBody : "Role needing method bodies should have base method set"; //$NON-NLS-1$ |
| // fake resolving: |
| callout.scope = new CallinCalloutScope(roleClass.scope, callout); |
| callout.scope.createBinding(callout); |
| // these create a return type reference from the return type binding: |
| callout.checkReturnCompatibility(callout.roleMethodSpec); |
| if (callout.baseMethodSpec != null) |
| callout.checkReturnCompatibility(callout.baseMethodSpec); |
| // translate to method declaration: |
| createCallout(callout, needBody, MethodModel.getImplementingInferredCallout(mapping._roleMethodBinding)!=null/*isInferred*/); |
| } |
| |
| /** |
| * Try to infer a callout binding that implements a given abstract method. |
| * |
| * @param roleClass type into which the callout might be generated (guaranteed to be a role) |
| * @param abstractMethod inherited abstract method |
| * @return whether or not inference was successful |
| */ |
| public boolean generateInferredCallout(TypeDeclaration roleClass, MethodBinding abstractMethod) { |
| ReferenceBinding baseType = this._role.getBaseTypeBinding(); |
| if (baseType == null || !baseType.isValidBinding()) |
| return false; |
| AstGenerator gen = new AstGenerator(roleClass.sourceStart, roleClass.sourceEnd); |
| MethodSpec roleMethod = fromMethodBinding(abstractMethod, gen); |
| TypeBinding[] roleParams = abstractMethod.parameters; |
| |
| return internalGenerateInferredCallout(roleClass, baseType, |
| roleMethod, roleParams, InferenceKind.INTERFACE, |
| gen); |
| } |
| |
| /** |
| * Try to infer a callout binding from an otherwise unresolved message send. |
| * |
| * @param roleClass unchecked |
| * @param messageSend |
| * @param roleParams |
| * @return whether or not inference succeeded |
| */ |
| public static boolean inferMappingFromCall(TypeDeclaration roleClass, |
| MessageSend messageSend, |
| TypeBinding[] roleParams) |
| { |
| if (roleClass == null || !roleClass.isRole()) |
| return false; |
| |
| if (!messageSend.receiver.isThis()) |
| return false; |
| |
| ReferenceBinding baseType = roleClass.binding.baseclass(); |
| if (baseType == null) |
| return false; |
| |
| AstGenerator gen = new AstGenerator(messageSend.sourceStart, messageSend.sourceEnd); |
| MethodSpec roleMethod = fromMessageSend(roleClass, messageSend, roleParams, gen); |
| |
| CalloutImplementor coi = new CalloutImplementor(roleClass.getRoleModel()); |
| if (coi.internalGenerateInferredCallout(roleClass, baseType, |
| roleMethod, roleParams, InferenceKind.SELFCALL, |
| gen)) |
| { |
| messageSend.binding = roleMethod.resolvedMethod; |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * After a role method spec has been created perform remaining tasks of |
| * inferring a base method and creating the callout mapping. |
| * (not used for inferred callout-to-field). |
| * |
| * @param roleClass guaranteed to be a bound role |
| * @param baseType non-null |
| * @param roleMethodSpec freshly created and manually resolved |
| * @param roleParams |
| * @param kind infer from superInterface or from an unresolved self-call? |
| * @param gen positioned AstGenerator |
| * @return whether or not inference succeeded |
| */ |
| private boolean internalGenerateInferredCallout(TypeDeclaration roleClass, |
| ReferenceBinding baseType, |
| MethodSpec roleMethodSpec, |
| TypeBinding[] roleParams, |
| InferenceKind kind, |
| AstGenerator gen) |
| { |
| CalloutMappingDeclaration callout; |
| callout= gen.calloutMappingDeclaration(roleClass.compilationResult); |
| callout.roleMethodSpec= roleMethodSpec; |
| // fake resolving: |
| callout.scope= new CallinCalloutScope(roleClass.scope, callout); |
| |
| // find matching candidate: |
| MethodBinding candidate; |
| candidate= inferBaseMethod(callout, baseType, roleMethodSpec.selector, roleParams); |
| if (candidate == null) |
| return false; |
| |
| // modifiers adjustment: |
| if (kind == InferenceKind.SELFCALL && candidate.isStatic()) |
| // no problem to adjust method binding which is generated in #fromMessageSend() |
| roleMethodSpec.resolvedMethod.modifiers |= AccStatic; |
| |
| // return type adjustments: |
| TypeBinding expectedType= roleMethodSpec.resolvedType(); |
| if (DependentTypeBinding.isDependentType(expectedType)) { |
| DependentTypeBinding roleBinding = (DependentTypeBinding)expectedType; |
| if (roleBinding.getAnchor() instanceof TThisBinding) |
| expectedType= roleBinding.baseclass(); |
| } |
| |
| // adjust return type from base method: |
| if ( expectedType != null |
| && !candidate.returnType.isCompatibleWith(expectedType)) |
| { |
| roleMethodSpec.returnType.resolvedType = candidate.returnType; |
| roleMethodSpec.resolvedMethod.returnType = candidate.returnType; |
| } |
| |
| // add thrown exceptions: |
| roleMethodSpec.resolvedMethod.thrownExceptions = candidate.thrownExceptions; |
| |
| if (kind == InferenceKind.SELFCALL) { |
| // adjust parameters from base method: |
| for (int i = 0; i < roleParams.length; i++) { |
| if (roleParams[i] != candidate.parameters[i]) { |
| Config.requireTypeAdjustment(); // reset |
| if ( roleParams[i].isCompatibleWith(candidate.parameters[i]) |
| && !Config.getLoweringRequired()) |
| { |
| roleMethodSpec.resolvedMethod.parameters[i] = candidate.parameters[i]; |
| } |
| } |
| } |
| } |
| |
| // continue generating: |
| callout.baseMethodSpec = fromMethodBinding(candidate, gen); |
| |
| // now we have all information for checking 3.4(d) (see Trac #96): |
| if (!callout.checkVisibility(callout.baseMethodSpec, baseType)) |
| return false; // don't generate illegal access. |
| |
| CallinCalloutBinding calloutBinding; |
| calloutBinding= callout.scope.createBinding(callout); |
| calloutBinding.inferred= kind; |
| calloutBinding._baseMethods = new MethodBinding[] {candidate}; |
| roleClass.binding.addCallinCallouts(new CallinCalloutBinding[]{calloutBinding}); |
| // these create a return type reference from the return type binding: |
| callout.checkReturnCompatibility(callout.roleMethodSpec); |
| callout.checkReturnCompatibility(callout.baseMethodSpec); |
| // translate to method declaration: |
| createCallout(callout, true/*needBody*/, true/*isInferred*/); |
| return true; |
| } |
| |
| /** |
| * |
| * @param callout role method spec is set, base is unset. |
| * @param baseType non-null |
| * @param selector |
| * @param roleParams |
| * @return whether or not inference succeeded |
| */ |
| private MethodBinding inferBaseMethod(CalloutMappingDeclaration callout, |
| ReferenceBinding baseType, |
| char[] selector, |
| TypeBinding[] roleParams) |
| { |
| ArrayList<MethodBinding> candidates = new ArrayList<MethodBinding>(); |
| while (baseType != null) { |
| try { |
| for (MethodBinding method : baseType.methods()) |
| if ( CharOperation.equals(method.selector, selector) |
| && method.parameters.length == roleParams.length) |
| { |
| candidates.add(method); |
| } |
| } catch (AbortCompilation ac) { |
| if (baseType.isBinaryBinding() && ac.problem.getID() == IProblem.IsClassPathCorrect) |
| return null; // binary type with missing dependencies, nothing we can infer |
| throw ac; // unknown reason, don't silently swallow |
| } |
| |
| for (MethodBinding candidate : candidates) |
| if (callout.internalCheckParametersCompatibility(null, roleParams, candidate.parameters)) |
| return candidate; |
| baseType= baseType.superclass(); |
| } |
| return null; |
| } |
| |
| private MethodSpec fromMethodBinding(MethodBinding method, AstGenerator gen) { |
| MethodSpec result = gen.methodSpec(method.selector); |
| result.resolvedMethod = method; |
| setInferredReturnType(result, method.returnType, gen); |
| result.initTranslationBits(); // needed for internalCheckParametersCompatibility() |
| return result; |
| } |
| |
| private static MethodSpec fromMessageSend(TypeDeclaration roleClass, |
| MessageSend send, |
| TypeBinding[] roleParams, |
| AstGenerator gen) |
| { |
| MethodSpec result = gen.methodSpec(send.selector); |
| TypeBinding expectedType = send.expectedType; |
| result.resolvedMethod = new MethodBinding(AccPrivate, send.selector, expectedType, roleParams, null, roleClass.binding); |
| setInferredReturnType(result, expectedType, gen); |
| result.initTranslationBits(); // needed for internalCheckParametersCompatibility() |
| return result; |
| } |
| private static void setInferredReturnType(MethodSpec methodSpec, TypeBinding expectedType, AstGenerator gen) |
| { |
| methodSpec.returnType = gen.singleTypeReference("<inferredType>".toCharArray()); //$NON-NLS-1$ |
| if (expectedType != null) |
| methodSpec.returnType.resolvedType = expectedType; |
| else |
| methodSpec.returnType.resolvedType = TypeBinding.VOID; |
| } |
| |
| /** |
| * API: If an expression failed to resolve a field, |
| * try whether using a callout-to-field could be substituted |
| * (potentially also inferring the callout itself). |
| * |
| * @param scope |
| * @param receiver can be null |
| * @param location |
| * @param fieldName |
| * @param isSetter |
| * @param expectedType what type does the caller of this inferred callout expect? May be null. |
| * @return whether or not inference succeeded |
| */ |
| public static CalloutMappingDeclaration inferCalloutAccess(Scope scope, Expression receiver, Expression location, char[] fieldName, boolean isSetter, TypeBinding expectedType) { |
| if (receiver != null && !(receiver instanceof ThisReference)) |
| return null; |
| TypeDeclaration type= scope.referenceType(); |
| if (!type.isRole() || !type.getRoleModel().isBound()) |
| return null; |
| char[] accessorName = OTNameUtils.accessorName(isSetter, fieldName); |
| |
| // search callout-to-field: |
| CalloutMappingDeclaration callout = null; |
| if (type.callinCallouts != null) { |
| for (AbstractMethodMappingDeclaration mapping : type.callinCallouts) { |
| if (!mapping.isCallout()) |
| continue; |
| CalloutMappingDeclaration candidate = (CalloutMappingDeclaration)mapping; |
| if (!candidate.isCalloutToField()) |
| continue; |
| FieldAccessSpec fieldAccessSpec = (FieldAccessSpec)candidate.baseMethodSpec; |
| if (fieldAccessSpec.isSetter() != isSetter) |
| continue; |
| FieldBinding baseField= fieldAccessSpec.resolvedField; |
| if (baseField == null || !baseField.isValidBinding()) |
| continue; |
| if ( CharOperation.equals(baseField.name, fieldName) |
| && CharOperation.equals(candidate.roleMethodSpec.selector, accessorName)) |
| { |
| // found |
| callout = candidate; |
| break; |
| } |
| } |
| } |
| if (callout == null) { // second chance: infer the callout binding, too: |
| AstGenerator gen= new AstGenerator(location.sourceStart, location.sourceEnd); |
| callout = inferCalloutToField(type, fieldName, accessorName, isSetter, expectedType, gen); |
| } |
| |
| if (callout != null) { |
| if ((location.bits & ASTNode.IsCompoundAssigned) != 0) { |
| // not legal in this context |
| scope.problemReporter().inferredCalloutInCompoundAssignment(location, fieldName); |
| return null; |
| } |
| scope.problemReporter().inferredUseOfCalloutToField(isSetter, location, fieldName, ((FieldAccessSpec)callout.baseMethodSpec).resolvedField); |
| } |
| return callout; |
| } |
| |
| private static CalloutMappingDeclaration inferCalloutToField(TypeDeclaration roleClass, |
| char[] fieldName, |
| char[] accessorName, |
| boolean isSetter, |
| TypeBinding expectedType, |
| AstGenerator gen) |
| { |
| ReferenceBinding baseclass= roleClass.binding.baseclass(); |
| if (baseclass == null || !baseclass.isValidBinding()) |
| return null; |
| |
| // find matching candidate: |
| FieldBinding baseField= TypeAnalyzer.findField(baseclass, fieldName, /*static*/false, false); |
| if (baseField == null) |
| return null; |
| |
| FieldModel fieldModel= FieldModel.getModel(baseField); |
| CalloutMappingDeclaration callout= isSetter ? |
| fieldModel._setterCallout : fieldModel._getterCallout; |
| if (callout != null) |
| return callout; // already generated |
| |
| // generate: |
| callout= gen.calloutMappingDeclaration(roleClass.compilationResult); |
| callout.hasSignature= true; |
| if (isSetter) { |
| fieldModel._setterCallout= callout; |
| fillInferredCalloutSetToField(callout, accessorName, baseField, gen); |
| } else { |
| fieldModel._getterCallout= callout; |
| fillInferredCalloutGetToField(callout, accessorName, baseField, expectedType, gen); |
| } |
| |
| // resolve: |
| callout.scope= new CallinCalloutScope(roleClass.scope, callout); |
| CallinCalloutBinding calloutBinding= callout.scope.createBinding(callout); |
| callout.resolveMethodSpecs(roleClass.getRoleModel(), baseclass, true); |
| |
| calloutBinding.inferred= isSetter ? InferenceKind.FIELDSET : InferenceKind.FIELDGET; |
| if (callout.baseMethodSpec.resolvedMethod != null) { |
| calloutBinding._baseMethods = new MethodBinding[] {callout.baseMethodSpec.resolvedMethod}; |
| } else { |
| calloutBinding._baseField = ((FieldAccessSpec)callout.baseMethodSpec).resolvedField; |
| } |
| roleClass.binding.addCallinCallouts(new CallinCalloutBinding[]{calloutBinding}); |
| |
| // translate to method declaration: |
| new CalloutImplementor(roleClass.getRoleModel()).createCallout(callout, true/*needBody*/, true/*isInferred*/); |
| |
| return callout; |
| } |
| |
| /* Create method specs for a callout "get" to field. */ |
| private static void fillInferredCalloutGetToField(CalloutMappingDeclaration callout, |
| char[] accessorName, |
| FieldBinding baseField, |
| TypeBinding expectedType, |
| AstGenerator gen) |
| { |
| MethodSpec roleMethodSpec; |
| roleMethodSpec = gen.methodSpec(accessorName); |
| roleMethodSpec.hasSignature= true; |
| callout.roleMethodSpec= roleMethodSpec; |
| roleMethodSpec.returnType= gen.typeReference(expectedType != null ? expectedType : baseField.type); |
| roleMethodSpec.arguments= new Argument[0]; |
| |
| FieldAccessSpec fieldAccessSpec; |
| fieldAccessSpec = gen.fieldAccessSpec(baseField.name, baseField.type, false); |
| fieldAccessSpec.hasSignature= true; |
| callout.baseMethodSpec= fieldAccessSpec; |
| } |
| |
| /* Create method specs for a callout "set" to field. */ |
| private static void fillInferredCalloutSetToField(CalloutMappingDeclaration callout, |
| char[] accessorName, |
| FieldBinding baseField, |
| AstGenerator gen) |
| { |
| MethodSpec roleMethodSpec; |
| roleMethodSpec = gen.methodSpec(accessorName); |
| roleMethodSpec.hasSignature= true; |
| callout.roleMethodSpec= roleMethodSpec; |
| roleMethodSpec.returnType= gen.singleTypeReference(TypeConstants.VOID); |
| roleMethodSpec.arguments= new Argument[] { |
| gen.argument(baseField.name, gen.typeReference(baseField.type)) |
| }; |
| |
| FieldAccessSpec fieldAccessSpec; |
| fieldAccessSpec = gen.fieldAccessSpec(baseField.name, baseField.type, true); |
| fieldAccessSpec.hasSignature= true; |
| callout.baseMethodSpec= fieldAccessSpec; |
| } |
| } |