Bug 416938 - [compiler] inferred callouts are not consistently available
in a tsub role 
- alternative strategy
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java
index ef696c1..2614134 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java
@@ -33,13 +33,9 @@
 import org.eclipse.jdt.internal.compiler.util.Sorting;
 import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
 import org.eclipse.objectteams.otdt.core.compiler.OTNameUtils;
-import org.eclipse.objectteams.otdt.internal.core.compiler.control.Dependencies;
-import org.eclipse.objectteams.otdt.internal.core.compiler.control.ITranslationStates;
-import org.eclipse.objectteams.otdt.internal.core.compiler.control.StateHelper;
 import org.eclipse.objectteams.otdt.internal.core.compiler.mappings.CalloutImplementor;
 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.statemachine.copyinheritance.CopyInheritance;
 import org.eclipse.objectteams.otdt.internal.core.compiler.util.Protections;
 import org.eclipse.objectteams.otdt.internal.core.compiler.util.TypeAnalyzer;
 
@@ -165,8 +161,9 @@
 //{ObjectTeams: try to infer a callout:
 			if (this.type.isRole()) {
 				CalloutImplementor coi = new CalloutImplementor(this.type.roleModel);
-				if (coi.generateInferredCallout(typeDeclaration, abstractMethod)) {
-					typeDeclaration.scope.problemReporter().addingInferredCalloutForInherited(typeDeclaration, abstractMethod);
+				MethodDeclaration callout = coi.generateInferredCallout(typeDeclaration, abstractMethod);
+				if (callout != null) {
+					typeDeclaration.scope.problemReporter().addingInferredCalloutForInherited(typeDeclaration, abstractMethod, callout);
 					return;
 				}
 			}
@@ -830,15 +827,6 @@
 	return false;   // the case for <= 1.4  (cannot differ)
 }
 void computeMethods() {
-//{ObjectTeams: make sure we actually have all methods we can have:
-	// supers (unless in danger of infinite recursion) should have all features:
-	ReferenceBinding superclass = this.type.superclass;
-	if (StateHelper.isDefinitelyReadyToProcess(superclass, this.type, ITranslationStates.STATE_METHODS_VERIFIED))
-		Dependencies.ensureBindingState(superclass, ITranslationStates.STATE_METHODS_VERIFIED);
-	// role should copy all we can get by now
-	if (this.type.isRole() && !this.type.isInterface())
-		CopyInheritance.copyGeneratedFeatures(this.type.roleModel);
-// SH}
 
 	MethodBinding[] methods = this.type.methods();
 	int size = methods.length;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
index 6263740..048c406 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
@@ -113,6 +113,7 @@
 import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
 import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.impl.IrritantSet;
 import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
 import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
 import org.eclipse.jdt.internal.compiler.lookup.Binding;
@@ -10943,7 +10944,10 @@
 		methodSpec.declarationSourceEnd
 	);
 }
-public void addingInferredCalloutForInherited(TypeDeclaration type, MethodBinding abstractMethod) {
+public void addingInferredCalloutForInherited(TypeDeclaration type, MethodBinding abstractMethod, final MethodDeclaration calloutWrapper) {
+	setRechecker(new IProblemRechecker() { public boolean shouldBeReported(IrritantSet[] foundIrritants) {
+		return !calloutWrapper.isCopied; // inferred callout replaced by a copy-inherited method?
+	}});
 	String[] args = { new String(abstractMethod.shortReadableName()) };
 	this.handle(
 			IProblem.AddingInferredCalloutForInherited,
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/Dependencies.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/Dependencies.java
index 8e9fc4b..6cee693 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/Dependencies.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/Dependencies.java
@@ -1816,8 +1816,9 @@
 				if (((method.modifiers & ClassFileConstants.AccAbstract) != 0) && method.isCopied) {
 		    		// inheriting abstract method in non-abstract role may require callout inference:
 					CalloutImplementor coi = new CalloutImplementor(roleDecl.getRoleModel());
-					if (coi.generateInferredCallout(roleDecl, method.binding))
-						roleDecl.scope.problemReporter().addingInferredCalloutForInherited(roleDecl, method.binding);
+					MethodDeclaration callout = coi.generateInferredCallout(roleDecl, method.binding);
+					if (callout != null)
+						roleDecl.scope.problemReporter().addingInferredCalloutForInherited(roleDecl, method.binding, callout);
 				}
 			}
 		}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/mappings/CalloutImplementor.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/mappings/CalloutImplementor.java
index 288baf5..e7ab58e 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/mappings/CalloutImplementor.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/mappings/CalloutImplementor.java
@@ -161,7 +161,7 @@
 				if(methodMapping.isCallout())
 				{
 					boolean createStatements= needMethodBodies && !methodMapping.hasErrors();
-					result &= createCallout((CalloutMappingDeclaration) methodMapping, createStatements, false/*inferred*/);
+					result &= (createCallout((CalloutMappingDeclaration) methodMapping, createStatements, false/*inferred*/) != null);
 				}
 			}
 		}
@@ -170,7 +170,7 @@
 	}
 
     /** This method drives the creation of a callout implementation for one callout mapping. */
-    private boolean createCallout(CalloutMappingDeclaration calloutMappingDeclaration, boolean needBody, boolean isInferred)
+    private MethodDeclaration createCallout(CalloutMappingDeclaration calloutMappingDeclaration, boolean needBody, boolean isInferred)
     {
 		CallinCalloutScope calloutScope = calloutMappingDeclaration.scope;
 
@@ -183,14 +183,14 @@
         {
         	// problemreporting already done in find-Base/Role-MethodBinding
             assert(calloutMappingDeclaration.ignoreFurtherInvestigation);
-			return false;
+			return null;
         }
         if (!roleMethodBinding.isValidBinding()) {
         	if (roleMethodBinding.problemId() != ProblemReasons.NotFound) {
         		// CLOVER: never true in jacks suite
 	        	calloutMappingDeclaration.tagAsHavingErrors();
 	        	// hopefully error has been reported!
-	        	return false;
+	        	return null;
 	        } else { // shorthand style callout
 	        	// help sourceMethod() below to find another callout method
 	        	MethodBinding existingMethod = calloutScope.enclosingSourceType().getExactMethod(roleMethodBinding.selector, roleMethodBinding.parameters, calloutScope.compilationUnitScope());
@@ -281,7 +281,7 @@
 	            		this._role.getAst(),
 						calloutMappingDeclaration,
 						roleMethodDeclaration.binding);
-	            return false;
+	            return null;
             }
 
             // fix flags (even if not needing body):
@@ -313,7 +313,7 @@
 			throw new InternalCompilerError("OT-Compiler Error: couldn't create method declaration for callout! "  //$NON-NLS-1$
 									+ calloutMappingDeclaration.toString());
     	}
-        return true;
+        return roleMethodDeclaration;
     }
 
     private MethodDeclaration createAbstractRoleMethodDeclaration(
@@ -1008,12 +1008,12 @@
 	 *
 	 * @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
+	 * @return the generated wrapper method or null
 	 */
-	public boolean generateInferredCallout(TypeDeclaration roleClass, MethodBinding abstractMethod) {
+	public MethodDeclaration generateInferredCallout(TypeDeclaration roleClass, MethodBinding abstractMethod) {
 		ReferenceBinding baseType = this._role.getBaseTypeBinding();
 		if (baseType == null || !baseType.isValidBinding())
-			return false;
+			return null;
 		AstGenerator gen = new AstGenerator(roleClass.sourceStart, roleClass.sourceEnd);
 		MethodSpec roleMethod = fromMethodBinding(abstractMethod, gen);
 		TypeBinding[] roleParams = abstractMethod.parameters;
@@ -1051,7 +1051,7 @@
 		CalloutImplementor coi = new CalloutImplementor(roleClass.getRoleModel());
 		if (coi.internalGenerateInferredCallout(roleClass, baseType,
 												roleMethod, roleParams, InferenceKind.SELFCALL,
-												gen))
+												gen) != null)
 		{
 			messageSend.binding = roleMethod.resolvedMethod;
 			return true;
@@ -1070,9 +1070,9 @@
 	 * @param roleParams
 	 * @param kind 			 infer from superInterface or from an unresolved self-call?
 	 * @param gen            positioned AstGenerator
-	 * @return whether or not inference succeeded
+	 * @return the generated wrapper method or null if no success
 	 */
-	private boolean internalGenerateInferredCallout(TypeDeclaration  roleClass,
+	private MethodDeclaration internalGenerateInferredCallout(TypeDeclaration  roleClass,
 													ReferenceBinding baseType,
 													MethodSpec 		 roleMethodSpec,
 													TypeBinding[]    roleParams,
@@ -1089,7 +1089,7 @@
 		MethodBinding candidate;
 		candidate= inferBaseMethod(callout, baseType, roleMethodSpec.selector, roleParams);
 		if (candidate == null)
-			return false;
+			return null;
 
 		// modifiers adjustment:
 		if (kind == InferenceKind.SELFCALL && candidate.isStatic())
@@ -1134,7 +1134,7 @@
 
 		// 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.
+			return null; // don't generate illegal access.
 
 		CallinCalloutBinding calloutBinding;
 		calloutBinding= callout.scope.createBinding(callout);
@@ -1145,8 +1145,7 @@
 		callout.checkReturnCompatibility(callout.roleMethodSpec);
 		callout.checkReturnCompatibility(callout.baseMethodSpec);
 		// translate to method declaration:
-		createCallout(callout, true/*needBody*/, true/*isInferred*/);
-		return true;
+		return createCallout(callout, true/*needBody*/, true/*isInferred*/);
 	}
 
 	/**
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/statemachine/copyinheritance/CopyInheritance.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/statemachine/copyinheritance/CopyInheritance.java
index 04ed96e..a2f090a 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/statemachine/copyinheritance/CopyInheritance.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/statemachine/copyinheritance/CopyInheritance.java
@@ -1017,6 +1017,7 @@
 	    // + adjust method in subteam to the more general signature (here)
 	    // + extend copy with marker arg (below).
 	    // else give an exact copy.
+	    boolean reuseFoundMethod = false;
 	    if(methodFound != null)
 	    {
 	        // do not touch broken methods:
@@ -1028,6 +1029,22 @@
 	    	if (methodFound.binding.copyInheritanceSrc == origin)
 	    		return; // diamond inheritance: copying the same method from two different sources
 
+	    	// tsuper method trumps previously inferred callout:
+	    	if (methodFound.isMappingWrapper == WrapperKind.CALLOUT) {
+	    		MethodModel model = methodFound.model;
+	    		if (model != null && model._inferredCallout != null) {
+	    			// reset callout related stuff:
+	    			methodFound.isMappingWrapper = WrapperKind.NONE;
+	    			model._inferredCallout = null;
+	    			methodFound.statements = null;
+	    			// setup as a copied method:
+	    			methodFound.isCopied = true;
+	    			methodFound.binding.copyInheritanceSrc = method;
+	    			methodFound.sourceMethodBinding = method;
+	    			reuseFoundMethod = true;
+	    		}
+	    	}
+
 	    	// do this in any case so that ConstantPoolObjectMapper won't fail:
 	    	methodFound.binding.addOverriddenTSuper(method);
 
@@ -1069,30 +1086,34 @@
 	    }
 	    AstGenerator gen = new AstGenerator(targetRoleDecl.sourceStart, targetRoleDecl.sourceEnd);
 	    gen.replaceableEnclosingClass = tgtTeam;
-	    final AbstractMethodDeclaration newMethodDecl =
-	    		AstConverter.createMethod(method, site, targetRoleDecl.compilationResult, DecapsulationState.REPORTED, gen);
+	    final AbstractMethodDeclaration newMethodDecl;
+	    if (methodFound != null && reuseFoundMethod) {
+	    	newMethodDecl = methodFound;
+	    } else {
+	    	newMethodDecl = AstConverter.createMethod(method, site, targetRoleDecl.compilationResult, DecapsulationState.REPORTED, gen);
 
-	    if (methodFound != null)
-	        TSuperHelper.addMarkerArg(newMethodDecl, srcTeam);
-
-	    if(newMethodDecl.isConstructor()){
-	        // comments (SH):
-	        // other phases may depend on this field (constructorCall) being set,
-	        // although it carries no real information.
-	        ConstructorDeclaration cd = (ConstructorDeclaration)newMethodDecl;
-	        cd.constructorCall = SuperReference.implicitSuperConstructorCall();
-
-	        if (   Lifting.isLiftingCtor(method)
-		    	&& method.parameters[0].isRole())
-		    {
-	        	// if baseclass is implicitely redefined use the strong type:
-	        	ReferenceBinding newBase= targetRoleDecl.binding.baseclass();
-	        	if (newBase != method.parameters[0])
-	        		newMethodDecl.arguments[0].type= gen.baseclassReference(newBase);
+		    if (methodFound != null)
+		        TSuperHelper.addMarkerArg(newMethodDecl, srcTeam);
+	
+		    if(newMethodDecl.isConstructor()){
+		        // comments (SH):
+		        // other phases may depend on this field (constructorCall) being set,
+		        // although it carries no real information.
+		        ConstructorDeclaration cd = (ConstructorDeclaration)newMethodDecl;
+		        cd.constructorCall = SuperReference.implicitSuperConstructorCall();
+	
+		        if (   Lifting.isLiftingCtor(method)
+			    	&& method.parameters[0].isRole())
+			    {
+		        	// if baseclass is implicitely redefined use the strong type:
+		        	ReferenceBinding newBase= targetRoleDecl.binding.baseclass();
+		        	if (newBase != method.parameters[0])
+		        		newMethodDecl.arguments[0].type= gen.baseclassReference(newBase);
+			    }
 		    }
-	    }
 
-    	AstEdit.addMethod(targetRoleDecl, newMethodDecl, wasSynthetic, false/*addToFront*/, origin);
+		    AstEdit.addMethod(targetRoleDecl, newMethodDecl, wasSynthetic, false/*addToFront*/, origin);
+	    }
     	if (method.isPrivate()) {
     		newMethodDecl.binding.modifiers |= ExtraCompilerModifiers.AccLocallyUsed; // don't warn unused copied method
     		MethodBinding synthBinding = SyntheticRoleBridgeMethodBinding.findOuterAccessor(targetRoleDecl.scope, targetRoleDecl.binding, newMethodDecl.binding);
diff --git a/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/calloutbinding/CalloutMethodBinding.java b/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/calloutbinding/CalloutMethodBinding.java
index 21b3b73..5b66d35 100644
--- a/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/calloutbinding/CalloutMethodBinding.java
+++ b/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/calloutbinding/CalloutMethodBinding.java
@@ -3129,7 +3129,7 @@
 			    "public team class Team3117ic14_1 {\n" + 
 			    "\n" + 
 			    "	@SuppressWarnings(\"inferredcallout\")\n" + 
-			    "	protected class Role1 implements IBase playedBy BaseClass {\n" + 
+			    "	protected class Role1 implements IBase playedBy BaseClass {\n" +
 			    "		String extra;\n" + 
 			    "	}\n" + 
 			    "}\n",
@@ -3145,6 +3145,102 @@
             customOptions);
     }
 
+    // Bug 416938 - [compiler] inferred callouts are not consistently available in a tsub role
+    // ensure subteam doesn't have to repeat callout inference (expect no warning!)
+    // positive variant with execution
+    public void test3117_inferredCallout14b() {
+       Map customOptions = getCompilerOptions();
+       customOptions.put(CompilerOptions.OPTION_ReportInferredCallout, CompilerOptions.WARNING);
+       
+       runConformTest(
+            new String[] {
+		"b/IBase.java",
+			    "\n" +
+			    "package b;\n" + 
+			    "\n" + 
+			    "public interface IBase {\n" + 
+			    "\n" + 
+			    "	void setS(String s);\n" + 
+			    "\n" +
+			    "	void setL(java.util.List<String> l);\n" + 
+			    "}\n",
+		"b/BaseClass.java",
+			    "package b;\n" + 
+			    "\n" + 
+			    "public class BaseClass implements IBase {\n" + 
+			    "\n" + 
+			    "	String s = \"OK\";\n" + 
+			    "	java.util.List<String> l;\n" + 
+			    "\n" + 
+			    "	public void setS(String s) {\n" + 
+			    "		this.s = s;\n" + 
+			    "	}\n" + 
+			    "\n" + 
+			    "	public void setL(java.util.List<String> l) {\n" + 
+			    "		this.l = l;\n" + 
+			    "	}\n" +
+			    "	public void print() {\n" +
+			    "		System.out.print(s);\n" +
+			    "		System.out.print(l.size());\n" +
+			    "	}\n" + 
+			    "}\n" + 
+			    "\n"
+            },
+            "",
+            null/*classLibraries*/,
+            true/*shouldFlushOutputDirectory*/,
+            null/*vmArguments*/,
+            customOptions,
+            null/*no custom requestor*/);
+       runConformTest(
+               new String[] {
+        "T3117ic14bMain.java",
+        		"public class T3117ic14bMain{\n" +
+        		"	public static void main(String[] args) {\n" +
+        		"		b.BaseClass b = new b.BaseClass();\n" +
+        		"		new t.Team3117ic14_2().test(b);\n" +
+        		"		b.print();\n" +
+        		"	}\n" +
+        		"}\n",
+		"t/Team3117ic14_2.java",
+			    "package t;\n" + 
+			    "\n" + 
+			    "import base b.BaseClass;\n" + 
+			    "\n" + 
+			    "public team class Team3117ic14_2 extends Team3117ic14_1 {\n" + 
+			    "   @Override\n" + 
+			    "	protected class Role1 {\n" +
+			    "       @Override public String toString() { return \"R\"; }\n" + 
+			    "	}\n" +
+			    "	public void test(BaseClass as Role1 r) {\n" +
+			    "		r.setS(\"NOK\");\n" +
+			    "		r.setL(new java.util.ArrayList<String>());\n" +
+			    "	}\n" + 
+			    "}\n",
+		"t/Team3117ic14_1.java",
+			    "package t;\n" + 
+			    "\n" + 
+			    "import b.IBase;\n" + 
+			    "\n" + 
+			    "import base b.BaseClass;\n" + 
+			    "\n" + 
+			    "public team class Team3117ic14_1 {\n" + 
+			    "\n" + 
+			    "	@SuppressWarnings(\"inferredcallout\")\n" + 
+			    "	protected class Role1 implements IBase playedBy BaseClass {\n" +
+			    "		public void setS(String s){}\n" + 
+			    "		String extra;\n" + 
+			    "	}\n" + 
+			    "}\n",
+            },
+            "OK0",
+            null/*classLibraries*/,
+            false/*shouldFlushOutputDirectory*/,
+            null/*vmArguments*/,
+            customOptions,
+            null/*no custom requestor*/);
+    }
+
     // a short callout binding lacks a rhs
     // 3.1.18-otjld-incomplete-callout-binding-1
     public void test3118_incompleteCalloutBinding1() {