Bug 569756 - Revisit field initialization in roles - discriminate final
/ non-final
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java
index 50ef142..9fd2f08 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java
@@ -53,6 +53,7 @@
 import org.eclipse.objectteams.otdt.internal.core.compiler.lifting.Lifting;
 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.TypeModel;
 import org.eclipse.objectteams.otdt.internal.core.compiler.model.MethodModel.ProblemDetail;
 import org.eclipse.objectteams.otdt.internal.core.compiler.statemachine.transformer.InsertTypeAdjustmentsVisitor;
 import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstGenerator;
@@ -346,7 +347,7 @@
 					&& this.constructorCall.accessMode != ExplicitConstructorCall.Tsuper) {
 				for (ReferenceBinding tsuperRole : roleType.roleModel.getTSuperRoleBindings()) {
 					RoleModel tsuperModel = tsuperRole.roleModel;
-					if (tsuperModel != null && tsuperModel.hasFieldInit()) {
+					if (tsuperModel != null && tsuperModel.hasFinalFieldInit()) {
 						classScope.problemReporter().needToCallTSuper(this.constructorCall, tsuperRole);
 						break;
 					}
@@ -623,7 +624,7 @@
 		initializerScope.computeLocalVariablePositions(argSlotSize, codeStream); // offset by the argument size (since not linked to method scope)
 
 		boolean needFieldInitializations = this.constructorCall == null || this.constructorCall.accessMode != ExplicitConstructorCall.This;
-//{ObjectTeams: some more constructors do not initialize fields:
+//{ObjectTeams: some deviations regarding field initialization:
 		// copied team constructors (due to arg lifting) do not initialize fields
 		if (   !needFieldInitializations
 			&& this.constructorCall != null
@@ -653,6 +654,9 @@
 			if (!preInitSyntheticFields){
 				generateSyntheticFieldInitializationsIfNecessary(this.scope, codeStream, declaringClass);
 			}
+//{ObjectTeams: if a role can make use of _OT$InitFields, that is where all fields are initialized (only if all are non-final):
+		  if (!isRoleUsingInitFields(declaringType, declaringClass)) {
+// orig:
 			// generate user field initialization
 			if (declaringType.fields != null) {
 				for (int i = 0, max = declaringType.fields.length; i < max; i++) {
@@ -662,6 +666,29 @@
 					}
 				}
 			}
+// :giro
+		  } else
+			callInit:
+		  {
+				// lifting ctor already contains the invoke statement
+				MethodBinding[] initMethods = declaringType.binding.getMethods(IOTConstants.INIT_METHOD_NAME);
+				if (initMethods.length >= 1)
+				{
+					int argCount = TSuperHelper.isTSuper(this.binding) ?  1 : 0;
+					for (int i = 0; i < initMethods.length; i++) {
+						if (initMethods[i].parameters.length == argCount) {
+							codeStream.aload_0(); // this
+							codeStream.invoke(Opcodes.OPC_invokevirtual, initMethods[i], declaringType.binding);
+							break callInit;
+						}
+					}
+				}
+				// no matching role init method should mean we had errors.
+				assert    TypeModel.isIgnoreFurtherInvestigation(classScope.referenceContext)
+				       || RoleModel.hasTagBit(declaringClass, RoleModel.BaseclassHasProblems)
+				       || declaringClass.isTeam(); // might be the "turning constructor" of a nested team (see 2.1.11-otjld-*-1f)
+		  }
+// SH}
 		}
 		// generate statements
 		if (this.statements != null) {
@@ -696,6 +723,23 @@
 	}
 	classFile.completeMethodInfo(this.binding, methodAttributeOffset, attributeNumber);
 }
+private boolean isRoleUsingInitFields(TypeDeclaration typeDecl, ReferenceBinding typeBinding) {
+	if (!typeDecl.isRole()
+		  || (   typeBinding.enclosingType() != null // also accept roles of o.o.Team
+			  && typeBinding.enclosingType().id == IOTConstants.T_OrgObjectTeamsTeam))
+		return false;
+	if (typeDecl.fields == null)
+		return false;
+	boolean hasInitialization = false;
+	for (FieldDeclaration fieldDeclaration : typeDecl.fields) {
+		if (fieldDeclaration.initialization != null) {
+			if (fieldDeclaration.isFinal())
+				return false; // needs to be set inside a proper constructor!
+			hasInitialization = true;
+		}
+	}
+	return hasInitialization;
+}
 
 @Override
 protected AnnotationBinding[][] getPropagatedRecordComponentAnnotations() {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java
index b5fc6b3..b8051a1 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java
@@ -2123,7 +2123,7 @@
 //{ObjectTeams:	should we work at all?
 	    Config config = Config.getConfig();
 	    boolean fieldsAndMethods = config != null && config.verifyMethods;
-	    boolean hasFieldInit = false;
+	    boolean hasFinalFieldInit = false;
 	  if (fieldsAndMethods) {
 // SH}
 		if (this.recordComponents != null) {
@@ -2160,8 +2160,8 @@
 						localMaxFieldCount++;
 						lastVisibleFieldID = field.binding.id;
 //{ObjectTeams: has init?
-						if (field.initialization != null)
-							hasFieldInit = true;
+						if (field.initialization != null && field.isFinal())
+							hasFinalFieldInit = true;
 // SH}
 						break;
 
@@ -2175,8 +2175,8 @@
 //{ObjectTeams: also count type value parameters into maxFieldCount
 		if (this.typeParameters != null)
 			TypeValueParameter.updateMaxFieldCount(this);
-		if (hasFieldInit && isRole())
-	        WordValueAttribute.addClassFlags(getRoleModel(), IOTConstants.OT_CLASS_HAS_FIELD_INITS);
+		if (hasFinalFieldInit && isRole())
+	        WordValueAttribute.addClassFlags(getRoleModel(), IOTConstants.OT_CLASS_HAS_FINAL_FIELD_INITS);
 	  }
 // SH}
 		if (this.maxFieldCount < localMaxFieldCount) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
index 2f68012..a7ec918 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
@@ -1235,7 +1235,7 @@
 123104 = Annotation '@{0}' can only be applied to role classes (OTJLD 2.3.1(d)).
 123105 = Fields are discouraged in roles with InstantiationPolicy '{0}' (OTJLD 2.3.1(d)).
 123106 = Roles with InstantiationPolicy '{0}' should define equals() and hashCode() methods (OTJLD 2.3.1(d)).
-123107 = Need to invoke a tsuper constructor because tsuper role ''{0}'' has field initializations.
+123107 = Need to invoke a tsuper constructor because tsuper role ''{0}'' has initializations for final fields.
 
 123201 = Illegal type for argument {0}: declared lifting not allowed in static methods (OTJLD 2.3.2(a)).
 123202 = Qualified type name not allowed here. Type after keyword "as" must be a role of the enclosing team {0} (OTJLD 2.3.2(a)).
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/core/compiler/IOTConstants.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/core/compiler/IOTConstants.java
index f025410..cd1ea31 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/core/compiler/IOTConstants.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/core/compiler/IOTConstants.java
@@ -96,6 +96,8 @@
 	public static final char[] OT_SETFIELD         = (OT_DOLLAR+"set$").toCharArray();
 	public static final String OT_DOLLAR_ARG       = OT_DOLLAR + "arg";
 
+	public static final char[] INIT_METHOD_NAME    = (OT_DOLLAR + "InitFields").toCharArray();
+
     // bytecode attributes:
     public static final String PLAYEDBY               = "PlayedBy";
     public static final char[] PLAYEDBY_NAME          = PLAYEDBY.toCharArray();
@@ -129,7 +131,7 @@
     public static final int    OT_CLASS_ROLE_FILE       = 16;
     public static final int    OT_CLASS_FLAG_HAS_TSUPER = 32;
     public static final int    OT_CLASS_CONFINED        = 64; // means: superclass Object should be removed on loading
-    public static final int    OT_CLASS_HAS_FIELD_INITS = 128;
+    public static final int    OT_CLASS_HAS_FINAL_FIELD_INITS = 128;
 
     public static final char[] CALLIN_FLAGS           = "CallinFlags".toCharArray();
       // possible values for CALLIN_FLAGS:
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/ast/RoleInitializationMethod.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/ast/RoleInitializationMethod.java
new file mode 100644
index 0000000..bf0ca8b
--- /dev/null
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/ast/RoleInitializationMethod.java
@@ -0,0 +1,237 @@
+/**********************************************************************
+ * This file is part of "Object Teams Development Tooling"-Software
+ *
+ * Copyright 2004, 2006 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: RoleInitializationMethod.java 23401 2010-02-02 23:56:05Z stephan $
+ *
+ * Please visit http://www.eclipse.org/objectteams for updates and contact.
+ *
+ * Contributors:
+ * Fraunhofer FIRST - Initial API and implementation
+ * Technical University Berlin - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.objectteams.otdt.internal.core.compiler.ast;
+
+import org.eclipse.jdt.internal.compiler.ClassFile;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+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.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
+import org.eclipse.jdt.internal.compiler.flow.InitializationFlowContext;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.eclipse.jdt.internal.compiler.parser.Parser;
+import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
+import org.eclipse.objectteams.otdt.internal.core.compiler.model.RoleModel;
+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.TSuperHelper;
+import org.eclipse.objectteams.otdt.internal.core.compiler.util.TypeAnalyzer;
+
+
+/**
+ * NEW for OTDT.
+ *
+ * A declaration for a generated method which performs all field initializations that
+ * otherwise each constructor would have to do.
+ *
+ * Example:
+ * 	 int i = 5;
+ * yields:
+ * 	 private void _OT$InitFields() {
+ *      tsuper._OT$InitFields(); // if defined.
+ * 		i = 5;
+ *   }
+ *   Role () { // any constructor..
+ *     super();
+ *     _OT$InitFields();
+ *     customStatements();
+ *   }
+ *
+ * Why: This greatly helps to merge field initializations during copy-inheritance
+ *      without modifying existing byte-code.
+ * How: endOfMethodHook() hooks into AbstractMethodDeclaration.generateCode().
+ *
+ * What: Insertion of calls to _OT$InitFields depends on the type of constructor:
+ *       1. Lifting constructors have an explicit MessageSend (generated by Lifting.genLiftToConstructorStatements)
+ *       2. Other constructors of roles invoke this method via ConstructorDeclaration.internalGenerateCode()
+ * Why:  Lifting constructors must assign _OT$base before executing any user code from field initializations
+ *       (_OT$base is needed for callouts!).
+ *
+ * @author stephan
+ * @version $Id: RoleInitializationMethod.java 23401 2010-02-02 23:56:05Z stephan $
+ */
+public class RoleInitializationMethod extends MethodDeclaration implements IOTConstants {
+
+	/**
+	 * @param compilationResult
+	 */
+	public RoleInitializationMethod(CompilationResult compilationResult) {
+		super(compilationResult);
+		this.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0, null);
+		this.selector = INIT_METHOD_NAME;
+		this.bits |= ASTNode.NeedFreeReturn;
+		this.isGenerated = true;
+		// set private to disable overriding along extends:
+		// (each constructor calls the corresponding init fields,
+		//  which must stay within the constructor's class).
+		this.modifiers = ClassFileConstants.AccPrivate;
+	}
+
+
+	/*
+	 * Override with empty implementation
+	 * (non-Javadoc)
+	 * @see org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration#analyseCode(org.eclipse.jdt.internal.compiler.lookup.ClassScope, org.eclipse.jdt.internal.compiler.flow.InitializationFlowContext, org.eclipse.jdt.internal.compiler.flow.FlowInfo)
+	 */
+	public void analyseCode(
+			ClassScope classScope,
+			InitializationFlowContext initializationContext,
+			FlowInfo info)
+	{
+		if (this.statements != null) {
+			if (this.binding != null)
+				this.binding.modifiers |= ExtraCompilerModifiers.AccLocallyUsed; // avoid unused-warning
+			super.analyseCode(classScope, initializationContext, info);
+		}
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration#parseStatements(org.eclipse.jdt.internal.compiler.parser.Parser, org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration)
+	 */
+	@Override
+	public void parseStatements(Parser parser, CompilationUnitDeclaration unit) {
+		// nothing to do, no statements to parse...
+		this.statements = new Statement[0];
+	}
+
+	/**
+	 * Instead of parsing statements, possibly generate them just before resolving.
+	 */
+	@Override
+	public void resolveStatements() {
+		if (this.ignoreFurtherInvestigation)
+			return;
+		MethodBinding tsuperInit = getTSuperInit(this.scope.referenceType().getRoleModel());
+		if (tsuperInit != null) {
+			AstGenerator gen = new AstGenerator(this.sourceStart, this.sourceEnd);
+			// there is a tsuper-version, so call it:
+			MessageSend tsuperCall = gen.messageSend(
+					ThisReference.implicitThis(),
+					this.selector,
+					null);
+			tsuperCall.arguments = TSuperHelper.addMarkerArgument(
+							null/*qualification*/, tsuperCall, tsuperCall.arguments, this.scope);
+			if (this.statements == null)
+				setStatements(new Statement[] { tsuperCall });
+			else {
+				// already have a casted local due to signature weakening
+				int len = this.statements.length;
+				Statement[] newStatements = new Statement[len+1];
+				System.arraycopy(
+						this.statements, 0,
+						newStatements, 0,
+						len);
+				newStatements[len] = tsuperCall;
+				setStatements(newStatements);
+			}
+			super.resolveStatements();
+		} else {
+			setStatements(new Statement[0]); // maybe remove useless casted local
+		}
+	}
+
+	/**
+	 * During generateCode this is invoked right before generating the return opcode.
+	 */
+	@Override
+	protected void endOfMethodHook (ClassFile classfile)
+	{
+		// basically taken from ConstructorDeclaration.internalGenerateCode
+		TypeDeclaration declaringType = this.scope.classScope().referenceContext;
+		// generate user field initialization
+		if (declaringType.fields != null) {
+			for (int i = 0, max = declaringType.fields.length; i < max; i++) {
+				FieldDeclaration fieldDecl;
+				if (!(fieldDecl = declaringType.fields[i]).isStatic()) {
+					fieldDecl.generateCode(this.scope, classfile.codeStream);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Create and setup a role initialization method.
+	 *
+	 * @param role the role class to augment (not an interface).
+	 */
+	public static void setupRoleInitializationMethod(RoleModel role) {
+		if (TypeAnalyzer.isTopConfined(role.getBinding()))
+			return;
+		// Each split role has a method _OT$initFields instead of initializing non-final fields directly from all constructors
+		// (for final fields the VM requires initialization to happen in the constructor, though (since Java9))
+		TypeDeclaration roleClassDeclaration = role.getAst();
+		AstGenerator gen = new AstGenerator(roleClassDeclaration.sourceStart, roleClassDeclaration.sourceEnd);
+		RoleInitializationMethod initMethod = gen.roleInitializationMethod(
+				roleClassDeclaration.compilationResult);
+		// no arguments
+		AstEdit.addMethod(roleClassDeclaration, initMethod);
+		if (!role.getBinding().isDirectRole())
+			initMethod.resolve(role.getAst().scope);
+	}
+
+
+	/**
+	 * Get the init fields method of the tsuper role of the current role OR null.
+	 * Be sure not to return a method that is already a tsuper-copy.
+	 *
+	 * TODO (SH): support multiple tsupers, call all their init methods
+	 *
+	 * @param role the current role
+	 * @return the tsuper version of this method.
+	 */
+	private MethodBinding getTSuperInit(RoleModel role) {
+		if (!role.getBinding().isDirectRole())
+			return null;
+		ReferenceBinding tsuperRole = role.getTSuperRoleBinding(); // TODO(SH) multiple tsupers
+		if (tsuperRole != null) {
+			if (TypeAnalyzer.isSourceTypeWithErrors(tsuperRole))
+				return null;
+			MethodBinding[] tsuperInits = tsuperRole.getMethods(INIT_METHOD_NAME);
+			if (tsuperInits != null && tsuperInits.length >= 1) {
+				for (int i=0; i<tsuperInits.length; i++) {
+					if (tsuperInits[i].parameters.length == 0)
+						return tsuperInits[i];
+				}
+			}
+		}
+		return null;
+	}
+
+	public static Statement genInvokeInitMethod(Expression receiver, ReferenceBinding receiverType, AstGenerator gen)
+	{
+		if (TypeAnalyzer.isTopConfined(receiverType))
+			return gen.emptyStatement();
+		return gen.messageSend(receiver, INIT_METHOD_NAME, null);
+	}
+}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/bytecode/ConstantPoolObjectMapper.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/bytecode/ConstantPoolObjectMapper.java
index 0b47776..ffb1a2e 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/bytecode/ConstantPoolObjectMapper.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/bytecode/ConstantPoolObjectMapper.java
@@ -474,6 +474,13 @@
 			assert(methods.length == 1);
 			return methods[0];
 		}
+		if (   role.isLocalType()
+			&& CharOperation.equals(refMethod.selector, IOTConstants.INIT_METHOD_NAME))
+		{
+			// have no overriding along implicit inheritance due to scoping of locals,
+			// so just retrieve the method by its name:
+			return role.getMethods(IOTConstants.INIT_METHOD_NAME)[0];
+		}
 		int bestRank = Integer.MAX_VALUE;
 		MethodBinding bestMethod = null;
 		methods = role.getMethods(refMethod.selector);
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 773c5b2..1a3817d 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
@@ -56,6 +56,7 @@
 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.RoleClassLiteralAccess;
+import org.eclipse.objectteams.otdt.internal.core.compiler.ast.RoleInitializationMethod;
 import org.eclipse.objectteams.otdt.internal.core.compiler.lifting.DeclaredLifting;
 import org.eclipse.objectteams.otdt.internal.core.compiler.lifting.Lifting.InstantiationPolicy;
 import org.eclipse.objectteams.otdt.internal.core.compiler.lifting.LiftingEnvironment;
@@ -608,6 +609,11 @@
                 	success = establishFaultInTypes(model);
                 	done = NESTED_TEAMS;
                 	break;
+                case STATE_ROLE_INIT_METHODS:
+					if (teamBinding.isRole())
+						ensureRoleState(teamBinding.roleModel, nextState);
+					done = NONE;
+                	break;
                 case STATE_METHODS_CREATED:
                 	success = establishMethodsCreated(model);
                 	done = NONE;
@@ -765,6 +771,9 @@
 						success &= ensureUnitState(model, nextState);
 					}
 					break;
+				case STATE_ROLE_INIT_METHODS:
+					success &= establishRoleInitializationMethod(model);
+					break;
                 case STATE_ROLE_FEATURES_COPIED:
                     success &= establishRoleFeaturesCopied(model);
                     break;
@@ -1016,6 +1025,7 @@
                 case STATE_NONE:
                 case STATE_ROLES_SPLIT:
                 case STATE_ROLES_LINKED:
+                case STATE_ROLE_INIT_METHODS:
                 case STATE_ROLE_FEATURES_COPIED:
                 case STATE_ROLE_HIERARCHY_ANALYZED:
                 case STATE_FULL_LIFTING:
@@ -1271,6 +1281,31 @@
 	 * => Parser.getMethodBodies()
 	 */
 
+	/* **** STATE_ROLE_INIT_METHODS (OT/J) ****
+	 * Create a method that holds all field initializations for a role.
+	 *
+	 * GENERATES:
+	 * - _OT$InitFields()
+	 */
+
+	/**
+     * BinaryTypes: nothing to do.
+     */
+	private static boolean establishRoleInitializationMethod(RoleModel role)
+	{
+		// must be done before role features copy
+		TypeDeclaration roleClassDeclaration = role.getAst();
+		if (   roleClassDeclaration != null
+			&& !roleClassDeclaration.isInterface()
+			&& needMethodBodies(roleClassDeclaration))
+		{
+			RoleInitializationMethod.setupRoleInitializationMethod(role);
+		}
+
+		role.setState(STATE_ROLE_INIT_METHODS);
+		return true;
+	}
+
 	/* **** STATE_ROLE_FEATURES_COPIED (OT/J) ****
      * Copy all direct features (methods/ctors/fields/) from tsuper roles.
      * Operate on AST and bindings. Link bindings of copied methods to
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/ITranslationStates.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/ITranslationStates.java
index 880e4ae..c4362c6 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/ITranslationStates.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/ITranslationStates.java
@@ -46,24 +46,22 @@
 	public static final int STATE_LENV_DONE_FIELDS_AND_METHODS= 7;//LookupEnvironment & Deps.
 	public static final int STATE_ROLES_LINKED            =  8;//LookupEnvironment & RoleSplitter
 	public static final int STATE_METHODS_PARSED          =  9;//Compiler
-// removed state:
-//	public static final int STATE_ROLE_INIT_METHODS       = 10;//RoleInitializationMethod
-//
     public static final int STATE_ROLE_HIERARCHY_ANALYZED = 10;//Lifting
-    public static final int STATE_ROLE_FEATURES_COPIED    = 11;//Copy inheritance
-	public static final int STATE_FULL_LIFTING            = 12;//Lifting
-    public static final int STATE_FAULT_IN_TYPES          = 13;//Scope
-    public static final int STATE_METHODS_CREATED         = 14;//RoleTypeBinding, CopyInheritance, CalloutImplementor
-    public static final int STATE_TYPES_ADJUSTED          = 15;//RoleTypeBinding, CopyInheritance
-    public static final int STATE_STATEMENTS_TRANSFORMED  = 16;//TransformStatementsVisitor
-    public static final int STATE_CALLINS_TRANSFORMED     = 17;//CallinImplementor[Dyn]
-    public static final int STATE_LATE_ATTRIBUTES_EVALUATED= 18; // ModelElement, TypeModel
-    public static final int STATE_METHODS_VERIFIED        = 19;//Scope
-    public static final int STATE_RESOLVED                = 20;//AST
-    public static final int STATE_LATE_ELEMENTS_COPIED    = 21;//CopyInheritance
-    public static final int STATE_CODE_ANALYZED           = 22;//AST
-    public static final int STATE_BYTE_CODE_PREPARED      = 23;//Dependencies
-    public static final int STATE_BYTE_CODE_GENERATED     = 24;//AST
+	public static final int STATE_ROLE_INIT_METHODS       = 11;//RoleInitializationMethod
+    public static final int STATE_ROLE_FEATURES_COPIED    = 12;//Copy inheritance
+	public static final int STATE_FULL_LIFTING            = 13;//Lifting
+    public static final int STATE_FAULT_IN_TYPES          = 14;//Scope
+    public static final int STATE_METHODS_CREATED         = 15;//RoleTypeBinding, CopyInheritance, CalloutImplementor
+    public static final int STATE_TYPES_ADJUSTED          = 16;//RoleTypeBinding, CopyInheritance
+    public static final int STATE_STATEMENTS_TRANSFORMED  = 17;//TransformStatementsVisitor
+    public static final int STATE_CALLINS_TRANSFORMED     = 18;//CallinImplementor[Dyn]
+    public static final int STATE_LATE_ATTRIBUTES_EVALUATED= 19; // ModelElement, TypeModel
+    public static final int STATE_METHODS_VERIFIED        = 20;//Scope
+    public static final int STATE_RESOLVED                = 21;//AST
+    public static final int STATE_LATE_ELEMENTS_COPIED    = 22;//CopyInheritance
+    public static final int STATE_CODE_ANALYZED           = 23;//AST
+    public static final int STATE_BYTE_CODE_PREPARED      = 24;//Dependencies
+    public static final int STATE_BYTE_CODE_GENERATED     = 25;//AST
     /**
      * Note, that this state does not trigger intermediate steps, in fact never use it
      * with ensureState() but directly invoke Dependencies.cleanup!
@@ -83,8 +81,9 @@
 		"bindings: fields and methods [completed]",
 		"roles linked",
 		"method bodies parsed",
-        "role hierarchy analyzed",
+		"role initialization method",
         "role features copied",
+        "role hierarchy analyzed",
         "full lifting",
         "fault in types",
         "methods created",
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/lifting/Lifting.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/lifting/Lifting.java
index bf181ca..d2db533 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/lifting/Lifting.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/lifting/Lifting.java
@@ -46,6 +46,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
 import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
 import org.eclipse.objectteams.otdt.core.exceptions.InternalCompilerError;
+import org.eclipse.objectteams.otdt.internal.core.compiler.ast.RoleInitializationMethod;
 import org.eclipse.objectteams.otdt.internal.core.compiler.control.ITranslationStates;
 import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.ITeamAnchor;
 import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.RoleTypeBinding;
@@ -413,8 +414,13 @@
                 new Expression[] {
                     gen.singleNameReference(baseArgName)
                 };
-        // start with an empty statements list
-        liftToConstructorDeclaration.setStatements(new Statement[0]);
+        // start with an empty statements list, except for this._OT$InitFields();
+        liftToConstructorDeclaration.setStatements(new Statement[] {
+        		RoleInitializationMethod.genInvokeInitMethod(
+					    					gen.thisReference(),
+					    					roleType.binding,
+					    					gen)
+        });
     }
 
     private void genLiftToConstructorStatements(
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/model/RoleModel.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/model/RoleModel.java
index a04322b..35d6a2b 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/model/RoleModel.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/model/RoleModel.java
@@ -36,6 +36,7 @@
 import org.eclipse.jdt.internal.compiler.ClassFile;
 import org.eclipse.jdt.internal.compiler.ast.ASTNode;
 import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
@@ -1134,7 +1135,10 @@
 	        this._tsuperRoleBindings[this.numTSuperRoles++] = tsuperRole;
 	        if (getAst() != null && getAst().isInterface())
 	        	TypeLevel.addImplicitInheritance(getAst(), tsuperRole);
-	        WordValueAttribute.addClassFlags(this, IOTConstants.OT_CLASS_FLAG_HAS_TSUPER);
+	        int otClassFlags = IOTConstants.OT_CLASS_FLAG_HAS_TSUPER;
+	        if (tsuperRole.roleModel.hasFinalFieldInit())
+	        	otClassFlags |= IOTConstants.OT_CLASS_HAS_FINAL_FIELD_INITS;
+			WordValueAttribute.addClassFlags(this, otClassFlags);
 	        if (this._binding != null)
 	        	this._binding.modifiers |= AccOverriding;
         }
@@ -1142,10 +1146,17 @@
         this._state.inititalize(ITranslationStates.STATE_ROLES_SPLIT);
     }
 
-    public boolean hasFieldInit() {
+    public boolean hasFinalFieldInit() {
     	AbstractAttribute attribute = getAttribute(IOTConstants.OT_CLASS_FLAGS);
     	if (attribute instanceof WordValueAttribute) {
-    		return (((WordValueAttribute) attribute).getValue() & IOTConstants.OT_CLASS_HAS_FIELD_INITS) != 0;
+    		if ((((WordValueAttribute) attribute).getValue() & IOTConstants.OT_CLASS_HAS_FINAL_FIELD_INITS) != 0)
+    			return true;
+    	}
+    	if (this._classPart != null && this._classPart.fields != null) {
+    		for (FieldDeclaration field : this._classPart.fields) {
+				if (field.initialization != null && field.isFinal())
+					return true;
+			}
     	}
     	return false;
     }
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 12438f1..7f27be0 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
@@ -2246,6 +2246,9 @@
                 }
             }
         }
+        if (    !binding.declaringClass.isDirectRole()
+        	&& CharOperation.equals(binding.selector, IOTConstants.INIT_METHOD_NAME))
+        	return changed; // no statements, no casted locals.
         if (!newLocalStats.isEmpty())
         {
         	if (StateHelper.hasState(binding.declaringClass, ITranslationStates.STATE_RESOLVED))
@@ -2348,7 +2351,7 @@
 	public static boolean needsSuperCtorCall(RoleModel role) {
 		for (ReferenceBinding tsuperRole : role.getTSuperRoleBindings()) {
 			RoleModel tsuperModel = tsuperRole.roleModel;
-			if (tsuperModel != null && tsuperModel.hasFieldInit())
+			if (tsuperModel != null && tsuperModel.hasFinalFieldInit())
 				return true;
 		}
 		return false;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/util/AstGenerator.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/util/AstGenerator.java
index 88f44b5..37c1582 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/util/AstGenerator.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/util/AstGenerator.java
@@ -65,6 +65,7 @@
 import org.eclipse.objectteams.otdt.internal.core.compiler.ast.PotentialLiftExpression;
 import org.eclipse.objectteams.otdt.internal.core.compiler.ast.QualifiedBaseReference;
 import org.eclipse.objectteams.otdt.internal.core.compiler.ast.ResultReference;
+import org.eclipse.objectteams.otdt.internal.core.compiler.ast.RoleInitializationMethod;
 import org.eclipse.objectteams.otdt.internal.core.compiler.ast.TSuperMessageSend;
 import org.eclipse.objectteams.otdt.internal.core.compiler.ast.TsuperReference;
 import org.eclipse.objectteams.otdt.internal.core.compiler.ast.TypeAnchorReference;
@@ -1291,6 +1292,13 @@
 		return lifter;
 	}
 
+	public RoleInitializationMethod roleInitializationMethod(CompilationResult compilationResult)
+	{
+	    RoleInitializationMethod roleInit = new RoleInitializationMethod(compilationResult);
+	    setMethodPositions(roleInit);
+	    return roleInit;
+	}
+
 	public ThisReference thisReference() {
 		return new ThisReference(this.sourceStart, this.sourceEnd);
 	}
diff --git a/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/regression/DevelopmentExamples.java b/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/regression/DevelopmentExamples.java
index c35f55a..746effe 100644
--- a/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/regression/DevelopmentExamples.java
+++ b/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/regression/DevelopmentExamples.java
@@ -5763,9 +5763,6 @@
 			    "	{\n" +
 			    "		buy     <- replace book;\n" +
 			    "		String getName() -> String getName();\n" +
-			    "		public Subscriber(flightbooking.model.Passenger p) {\n" +
-			    "			tsuper();\n" +
-			    "		}\n" +
 			    "	};\n" +
 			    "   @Override\n" +
 			    "	public class Item playedBy flightbooking.model.Segment {\n" +
@@ -5969,9 +5966,6 @@
 			    "	{\n" +
 			    "		buy     <- replace book;\n" +
 			    "		String getName() -> String getName();\n" +
-			    "		public Subscriber(Passenger p) {\n" +
-			    "			tsuper();\n" +
-			    "		}\n" +
 			    "	};\n" +
 			    "   @Override\n" +
 			    "	public class Item playedBy Segment {\n" +
diff --git a/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/rolesandteams/ImplicitInheritance.java b/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/rolesandteams/ImplicitInheritance.java
index e87e644..c5339af 100644
--- a/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/rolesandteams/ImplicitInheritance.java
+++ b/testplugins/org.eclipse.objectteams.otdt.tests/otjld/org/eclipse/objectteams/otdt/tests/otjld/rolesandteams/ImplicitInheritance.java
@@ -1478,6 +1478,7 @@
     			"	}\n" +
     			"	void test() {\n" +
     			"		System.out.print(new R1().s1);\n" + // OK: has explicit tsuper() call
+    			"		System.out.print(new R1().s1Final);\n" + // OK: has explicit tsuper() call
     			"		System.out.print(new R2().s2);\n" + // OK: fully inherited, all members are copied
     			"		System.out.print(new R3().s3);\n" + // OK: constructor is copied
     			"	}\n" +
@@ -1486,6 +1487,7 @@
     			"public team class SuperTeamFI4FI1 {\n" +
     			"	protected class R1 {\n" +
     			"		protected String s1 = \"s1\";\n" +
+    			"		final protected String s1Final = \"s1Final\";\n" +
     			"	}\n" +
     			"	protected class R2 {\n" +
     			"		protected String s2 = \"s2\";\n" +
@@ -1495,7 +1497,51 @@
     			"	}\n" +
     			"}\n",
     		},
-    		"s1s2s3");
+    		"s1s1Finals2s3");
+    }
+
+    public void testFieldInitialization1_OK_3layers() throws Exception {
+    	runConformTest(
+    		new String[] {
+    			"SubTeamFI1_3l.java",
+    			"public team class SubTeamFI1_3l extends MidTeamFI4FI1_3l {\n" +
+    			"	@Override\n" +
+    			"	protected class R1 {\n" +
+    			"		protected R1() {\n" +
+    			"			tsuper();\n" +
+    			"		}\n" +
+    			"	}\n" +
+    			"	@Override\n" +
+    			"	protected class R3 {\n" +
+    			"	}\n" +
+    			"	public static void main(String... args) {\n" +
+    			"		new SubTeamFI1_3l().test();\n" +
+    			"	}\n" +
+    			"	void test() {\n" +
+    			"		System.out.print(new R1().s1);\n" + // OK: has explicit tsuper() call
+    			"		System.out.print(new R1().s1Final);\n" + // OK: has explicit tsuper() call
+    			"		System.out.print(new R2().s2);\n" + // OK: fully inherited, all members are copied
+    			"		System.out.print(new R3().s3);\n" + // OK: constructor is copied
+    			"	}\n" +
+				"}\n",
+    			"MidTeamFI4FI1_3l.java",
+    			"public team class MidTeamFI4FI1_3l extends SuperTeamFI4FI1_3l {\n" +
+    			"}\n",
+    			"SuperTeamFI4FI1_3l.java",
+    			"public team class SuperTeamFI4FI1_3l {\n" +
+    			"	protected class R1 {\n" +
+    			"		protected String s1 = \"s1\";\n" +
+    			"		final protected String s1Final = \"s1Final\";\n" +
+    			"	}\n" +
+    			"	protected class R2 {\n" +
+    			"		protected String s2 = \"s2\";\n" +
+    			"	}\n" +
+    			"	protected class R3 {\n" +
+    			"		protected String s3 = \"s3\";\n" +
+    			"	}\n" +
+    			"}\n",
+    		},
+    		"s1s1Finals2s3");
     }
 
     public void testFieldInitialization2_bound_OK() throws Exception {
@@ -1549,6 +1595,93 @@
     		"s1s2s3");
     }
 
+    public void testFieldInitialization2_bound_OK_3layers() throws Exception {
+    	runConformTest(
+    		new String[] {
+    			"SubTeamFI2_3l.java",
+    			"public team class SubTeamFI2_3l extends MidTeamFI4FI2_3l {\n" +
+    			"	@Override\n" +
+    			"	protected class R1 {\n" +
+    			"		protected R1(Base1 b) {\n" +
+    			"			tsuper(b);\n" +
+    			"		}\n" +
+    			"	}\n" +
+    			"	@Override\n" +
+    			"	protected class R2 {\n" +
+    			"	}\n" +
+    			"	@Override\n" +
+    			"	protected class R3 {\n" +
+    			"		protected R3() {\n" +
+    			"			tsuper(base());\n" +
+    			"		}\n" +
+    			"	}\n" +
+    			"	public static void main(String... args) {\n" +
+    			"		new SubTeamFI2_3l().test();\n" +
+    			"	}\n" +
+    			"	void test() {\n" +
+    			"		System.out.print(new R1(new Base1()).s1);\n" + // OK: explicit tsuper() call (delegating lifting constructor)
+    			"		System.out.print(new R2(new Base2()).s2);\n" + // OK: copied lifting constructor
+    			"		System.out.print(new R3(new Base3()).s3);\n" + // OK: explicit tsuper() call (delegation from creating to lifting constructor)
+    			"	}\n" +
+				"}\n",
+    			"MidTeamFI4FI2_3l.java",
+    			"public team class MidTeamFI4FI2_3l extends SuperTeamFI4FI2_3l {\n" +
+    			"}\n",
+    			"SuperTeamFI4FI2_3l.java",
+    			"public team class SuperTeamFI4FI2_3l {\n" +
+    			"	protected class R1 playedBy Base1 {\n" +
+    			"		protected String s1 = \"s1\";\n" +
+    			"	}\n" +
+    			"	protected class R2 playedBy Base2 {\n" +
+    			"		protected String s2 = \"s2\";\n" +
+    			"	}\n" +
+    			"	protected class R3 playedBy Base3 {\n" +
+    			"		protected String s3 = \"s3\";\n" +
+    			"	}\n" +
+    			"}\n",
+    			"Base1.java",
+    			"public class Base1 {}\n",
+    			"Base2.java",
+    			"public class Base2 {}\n",
+    			"Base3.java",
+    			"public class Base3 {}\n",
+    		},
+    		"s1s2s3");
+    }
+
+    public void testFieldInitialization3_OK() throws Exception {
+    	runNegativeTest(
+    		new String[] {
+    			"SubTeamFI3a.java",
+    			"public team class SubTeamFI3a extends SuperTeamFI4FI3 {\n" +
+    			"	@Override\n" +
+    			"	protected class R1 {\n" +
+    			"		protected R1() {\n" +
+    			"			super();\n" +
+    			"		}\n" +
+    			"	}\n" +
+    			"	@Override\n" +
+    			"	protected class R2 {\n" +
+    			"		protected R2() {\n" +
+    			"			super();\n" +
+    			"		}\n" +
+    			"		protected R2(int i) {\n" + 
+    			"		}\n" +
+    			"	}\n" +
+				"}\n",
+				"SuperTeamFI4FI3.java",
+				"public team class SuperTeamFI4FI3 {\n" +
+				"	protected class R1 {\n" +
+				"		String s1 = \"s1\";\n" +
+				"	}\n" +
+				"	protected class R2 extends R1 {\n" +
+				"		String s2 = \"s2\";\n" +
+				"	}\n" +
+				"}\n"
+    		},
+    		"");
+    }
+
     public void testFieldInitialization3_NOK() throws Exception {
     	runNegativeTest(
     		new String[] {
@@ -1572,10 +1705,10 @@
 				"SuperTeamFI4FI3.java",
 				"public team class SuperTeamFI4FI3 {\n" +
 				"	protected class R1 {\n" +
-				"		String s1 = \"s1\";\n" +
+				"		final String s1 = \"s1\";\n" +
 				"	}\n" +
 				"	protected class R2 extends R1 {\n" +
-				"		String s2 = \"s2\";\n" +
+				"		final String s2 = \"s2\";\n" +
 				"	}\n" +
 				"}\n"
     		},
@@ -1583,20 +1716,114 @@
 			"1. ERROR in SubTeamFI3a.java (at line 5)\n" +
 			"	super();\n" +
 			"	^^^^^^^^\n" +
-			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI4FI3.R1\' has field initializations.\n" +
+			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI4FI3.R1\' has initializations for final fields.\n" +
 			"----------\n" +
 			"2. ERROR in SubTeamFI3a.java (at line 11)\n" +
 			"	super();\n" +
 			"	^^^^^^^^\n" +
-			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI4FI3.R2\' has field initializations.\n" +
+			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI4FI3.R2\' has initializations for final fields.\n" +
 			"----------\n" +
 			"3. ERROR in SubTeamFI3a.java (at line 13)\n" +
 			"	protected R2(int i) {\n" +
 			"	          ^^^^^^^^^\n" +
-			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI4FI3.R2\' has field initializations.\n" +
+			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI4FI3.R2\' has initializations for final fields.\n" +
 			"----------\n");
     }
 
+    public void testFieldInitialization3_NOK_3layers() throws Exception {
+    	runNegativeTest(
+    		new String[] {
+    			"SubTeamFI3a_3l.java",
+    			"public team class SubTeamFI3a_3l extends MidTeamFI4FI3a_3l {\n" +
+    			"	@Override\n" +
+    			"	protected class R1 {\n" +
+    			"		protected R1() {\n" +
+    			"			super();\n" +
+    			"		}\n" +
+    			"	}\n" +
+    			"	@Override\n" +
+    			"	protected class R2 {\n" +
+    			"		protected R2() {\n" +
+    			"			super();\n" +
+    			"		}\n" +
+    			"		protected R2(int i) {\n" + // implicit super() is not acceptable
+    			"		}\n" +
+    			"	}\n" +
+				"}\n",
+				"MidTeamFI4FI3a_3l.java",
+				"public team class MidTeamFI4FI3a_3l extends SuperTeamFI4FI3a_3l {\n" +
+				"}\n",
+				"SuperTeamFI4FI3a_3l.java",
+				"public team class SuperTeamFI4FI3a_3l {\n" +
+				"	protected class R1 {\n" +
+				"		final String s1 = \"s1\";\n" +
+				"	}\n" +
+				"	protected class R2 extends R1 {\n" +
+				"		final String s2 = \"s2\";\n" +
+				"	}\n" +
+				"}\n"
+    		},
+    		"----------\n" +
+			"1. ERROR in SubTeamFI3a_3l.java (at line 5)\n" +
+			"	super();\n" +
+			"	^^^^^^^^\n" +
+			"Need to invoke a tsuper constructor because tsuper role \'MidTeamFI4FI3a_3l.R1\' has initializations for final fields.\n" +
+			"----------\n" +
+			"2. ERROR in SubTeamFI3a_3l.java (at line 11)\n" +
+			"	super();\n" +
+			"	^^^^^^^^\n" +
+			"Need to invoke a tsuper constructor because tsuper role \'MidTeamFI4FI3a_3l.R2\' has initializations for final fields.\n" +
+			"----------\n" +
+			"3. ERROR in SubTeamFI3a_3l.java (at line 13)\n" +
+			"	protected R2(int i) {\n" +
+			"	          ^^^^^^^^^\n" +
+			"Need to invoke a tsuper constructor because tsuper role \'MidTeamFI4FI3a_3l.R2\' has initializations for final fields.\n" +
+			"----------\n");
+    }
+
+    public void testFieldInitialization4_bound_OK() throws Exception {
+    	// variants with regular constructors
+    	runNegativeTest(
+    		new String[] {
+    			"SubTeamFI4.java",
+    			"public team class SubTeamFI4 extends SuperTeamFI4 {\n" +
+    			"	@Override\n" +
+    			"	protected class R1 {\n" +
+    			"		protected R1() {\n" +
+    			"			tsuper(base());\n" +
+    			"		}\n" +
+    			"	}\n" +
+    			"	@Override\n" +
+    			"	protected class R2 {\n" +
+    			"		protected R2() {\n" +
+    			"			base();\n" +
+    			"		}\n" +
+    			"	}\n" +
+    			"	public static void main(String... args) {\n" +
+    			"		new SubTeamFI4().test();\n" +
+    			"	}\n" +
+    			"	void test() {\n" +
+    			"		System.out.print(new R1().s1);\n" +
+    			"		System.out.print(new R2().s2);\n" +
+    			"	}\n" +
+				"}\n",
+    			"SuperTeamFI4.java",
+    			"public team class SuperTeamFI4 {\n" +
+    			"	protected class R1 playedBy Base1 {\n" +
+    			"		protected String s1 = \"s1\";\n" +
+    			"	}\n" +
+    			"	protected class R2 playedBy Base2 {\n" +
+    			"		protected String s2 = \"s2\";\n" +
+    			"	}\n" +
+    			"}\n",
+    			"Base1.java",
+    			"public class Base1 {}\n",
+    			"Base2.java",
+    			"public class Base2 {}\n",
+    		},
+    		"");
+    }
+
     public void testFieldInitialization4_bound_NOK() throws Exception {
     	// variants with regular constructors
     	runNegativeTest(
@@ -1629,7 +1856,7 @@
     			"		protected String s1 = \"s1\";\n" +
     			"	}\n" +
     			"	protected class R2 playedBy Base2 {\n" +
-    			"		protected String s2 = \"s2\";\n" +
+    			"		final protected String s2 = \"s2\";\n" +
     			"	}\n" +
     			"}\n",
     			"Base1.java",
@@ -1646,11 +1873,67 @@
 			"2. ERROR in SubTeamFI4.java (at line 10)\n" +
 			"	protected R2() {\n" +
 			"	          ^^^^\n" +
-			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI4.R2\' has field initializations.\n" +
+			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI4.R2\' has initializations for final fields.\n" +
 			"----------\n");
     }
 
-    public void testFieldInitialization5_bound_NOK() throws Exception {
+    public void testFieldInitialization4_bound_NOK_3layers() throws Exception {
+    	// variants with regular constructors
+    	runNegativeTest(
+    		new String[] {
+    			"SubTeamFI4_3l.java",
+    			"public team class SubTeamFI4_3l extends MidTeamFI4_3l {\n" +
+    			"	@Override\n" +
+    			"	protected class R1 {\n" +
+    			"		protected R1() {\n" +
+    			"			tsuper();\n" +
+    			"		}\n" +
+    			"	}\n" +
+    			"	@Override\n" +
+    			"	protected class R2 {\n" +
+    			"		protected R2() {\n" +
+    			"			base();\n" +
+    			"		}\n" +
+    			"	}\n" +
+    			"	public static void main(String... args) {\n" +
+    			"		new SubTeamFI4_3l().test();\n" +
+    			"	}\n" +
+    			"	void test() {\n" +
+    			"		System.out.print(new R1().s1);\n" +
+    			"		System.out.print(new R2().s2);\n" +
+    			"	}\n" +
+				"}\n",
+    			"MidTeamFI4_3l.java",
+    			"public team class MidTeamFI4_3l extends SuperTeamFI4_3l {\n" +
+				"}\n",
+    			"SuperTeamFI4_3l.java",
+    			"public team class SuperTeamFI4_3l {\n" +
+    			"	protected class R1 playedBy Base1 {\n" +
+    			"		protected String s1 = \"s1\";\n" +
+    			"	}\n" +
+    			"	protected class R2 playedBy Base2 {\n" +
+    			"		final protected String s2 = \"s2\";\n" +
+    			"	}\n" +
+    			"}\n",
+    			"Base1.java",
+    			"public class Base1 {}\n",
+    			"Base2.java",
+    			"public class Base2 {}\n",
+    		},
+    		"----------\n" +
+			"1. ERROR in SubTeamFI4_3l.java (at line 5)\n" +
+			"	tsuper();\n" +
+			"	^^^^^^^^^\n" +
+			"The constructor MidTeamFI4_3l.R1() is undefined\n" +
+			"----------\n" +
+			"2. ERROR in SubTeamFI4_3l.java (at line 10)\n" +
+			"	protected R2() {\n" +
+			"	          ^^^^\n" +
+			"Need to invoke a tsuper constructor because tsuper role \'MidTeamFI4_3l.R2\' has initializations for final fields.\n" +
+			"----------\n");
+    }
+
+    public void testFieldInitialization5_bound_OK() throws Exception {
     	// variants with lifting constructors
     	runNegativeTest(
     		new String[] {
@@ -1681,16 +1964,50 @@
     			"Base1.java",
     			"public class Base1 {}\n",
     		},
+    		"");
+    }
+
+    public void testFieldInitialization5_bound_NOK() throws Exception {
+    	// variants with lifting constructors
+    	runNegativeTest(
+    		new String[] {
+    			"SubTeamFI5.java",
+    			"public team class SubTeamFI5 extends SuperTeamFI5 {\n" +
+    			"	@Override\n" +
+    			"	protected class R1 {\n" +
+    			"		protected R1(Base1 b) {\n" +
+    			"			super();\n" +
+    			"		}\n" +
+    			"	}\n" +
+    			"	@Override\n" +
+    			"	protected class R2 {\n" +
+    			"		protected R2(Base1 b) {\n" +
+    			"			super(b);\n" +
+    			"		}\n" +
+    			"	}\n" +
+				"}\n",
+    			"SuperTeamFI5.java",
+    			"public team class SuperTeamFI5 {\n" +
+    			"	protected class R1 playedBy Base1 {\n" +
+    			"		final String s1 = \"s1\";\n" +
+    			"	}\n" +
+    			"	protected class R2 extends R1 {\n" +
+    			"		final String s2 = \"s2\";\n" +
+    			"	}\n" +
+    			"}\n",
+    			"Base1.java",
+    			"public class Base1 {}\n",
+    		},
     		"----------\n" +
 			"1. ERROR in SubTeamFI5.java (at line 5)\n" +
 			"	super();\n" +
 			"	^^^^^^^^\n" +
-			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI5.R1\' has field initializations.\n" +
+			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI5.R1\' has initializations for final fields.\n" +
 			"----------\n" +
 			"2. ERROR in SubTeamFI5.java (at line 11)\n" +
 			"	super(b);\n" +
 			"	^^^^^^^^^\n" +
-			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI5.R2\' has field initializations.\n" +
+			"Need to invoke a tsuper constructor because tsuper role \'SuperTeamFI5.R2\' has initializations for final fields.\n" +
 			"----------\n");
     }
 }