Bug 400360 - [reconciler] fails to resolve callout-to-field with
path-anchored type
Solution: include necessary STATE_ROLES_LINKED into mngmt by lookupEnv
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
index 04818d3..08976f6 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
@@ -43,6 +43,7 @@
 import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.ITeamAnchor;
 import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.RoleTypeBinding;
 import org.eclipse.objectteams.otdt.internal.core.compiler.model.TeamModel;
+import org.eclipse.objectteams.otdt.internal.core.compiler.statemachine.transformer.RoleSplitter;
 import org.eclipse.objectteams.otdt.internal.core.compiler.statemachine.transformer.TeamMethodGenerator;
 import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstEdit;
 import org.eclipse.objectteams.otdt.internal.core.compiler.util.RoleFileHelper;
@@ -141,6 +142,9 @@
 	final static int BUILD_TYPE_HIERARCHY = 1;
 	final static int CHECK_AND_SET_IMPORTS = 2;
 	final static int CONNECT_TYPE_HIERARCHY = 3;
+//{ObjectTeams: include this step into LookupEnvironment's control:
+	final static int ROLES_LINKED = 5;
+// SH}
 
 	static final ProblemPackageBinding TheNotFoundPackage = new ProblemPackageBinding(CharOperation.NO_CHAR, NotFound);
 	static final ProblemReferenceBinding TheNotFoundType = new ProblemReferenceBinding(CharOperation.NO_CHAR_CHAR, null, NotFound);
@@ -326,10 +330,26 @@
 		done = true;
 	  }
 	  StateHelper.setStateRecursive(this.units[i], ITranslationStates.STATE_LENV_DONE_FIELDS_AND_METHODS, done);
-// SH}
+/* orig: defer to next loop:
 		this.units[i] = null; // release unnecessary reference to the parsed unit
+  :giro */
+// SH}
 	}
 	this.stepCompleted = BUILD_FIELDS_AND_METHODS;
+
+//{ObjectTeams: one more step to handle here:
+	for (int i = this.lastCompletedUnitIndex + 1; i <= this.lastUnitIndex; i++) {
+		boolean done = false;
+		if (this.units[i].state.getState() < ITranslationStates.STATE_ROLES_LINKED) {
+			RoleSplitter.linkRoles(this.units[i]);
+			done = true;
+		}
+		StateHelper.setStateRecursive(this.units[i], ITranslationStates.STATE_ROLES_LINKED, done);
+		this.units[i] = null; // release unnecessary reference to the parsed unit
+	}
+	this.stepCompleted = ROLES_LINKED;
+// SH}
+
 	this.lastCompletedUnitIndex = this.lastUnitIndex;
 	this.unitBeingCompleted = null;
 }
@@ -357,7 +377,12 @@
 		return 0; // avoid re-entrance
 	int todo = this.stepCompleted;
 //SH}
+//{ObjectTeams: different last step:
+/* orig:
 	if (this.stepCompleted == BUILD_FIELDS_AND_METHODS) {
+  :giro */
+	if (this.stepCompleted == ROLES_LINKED) {
+// SH}
 		// This can only happen because the original set of units are completely built and
 		// are now being processed, so we want to treat all the additional units as a group
 		// until they too are completely processed.
@@ -394,6 +419,10 @@
 // SH}
 			(this.unitBeingCompleted = parsedUnit).scope.connectTypeHierarchy();
 
+//{ObjectTeams: one more step:
+		if (todo >= ROLES_LINKED)
+			RoleSplitter.linkRoles(this.unitBeingCompleted = parsedUnit);
+
 		this.unitBeingCompleted = null;
 	}
 //{ObjectTeams: report actual step achieved:
@@ -1848,6 +1877,8 @@
 		return ITranslationStates.STATE_LENV_CONNECT_TYPE_HIERARCHY;
 	case BUILD_FIELDS_AND_METHODS:
 		return ITranslationStates.STATE_LENV_DONE_FIELDS_AND_METHODS;
+	case ROLES_LINKED:
+		return ITranslationStates.STATE_ROLES_LINKED;
 	}
 	return ITranslationStates.STATE_NONE;
 }
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 73fe3b4..8e9fc4b 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
@@ -305,6 +305,7 @@
 		    	case STATE_LENV_CHECK_AND_SET_IMPORTS:
 		    	case STATE_LENV_CONNECT_TYPE_HIERARCHY:
 		    	case STATE_LENV_DONE_FIELDS_AND_METHODS:
+		    	case STATE_ROLES_LINKED:
 		    		checkReadKnownRoles(unit);
 		    		LookupEnvironment lookupEnvironment= Config.getLookupEnvironment();
 		    		if (Config.getBundledCompleteTypeBindingsMode()) {
@@ -336,6 +337,8 @@
 	            		if (Config.getBuildFieldsAndMethods())
 	            			unit.scope.buildFieldsAndMethods();
 	            		break;
+	            	case STATE_ROLES_LINKED:
+	            		RoleSplitter.linkRoles(unit);
 	    			}
 		    		break;
 		        case STATE_METHODS_PARSED:
@@ -734,10 +737,15 @@
 					success &= establishRoleSplit(model);
 					break;
 				case STATE_ROLES_LINKED:
-					RoleSplitter.linkScopes(model);
-					RoleSplitter.linkSuperAndBaseInIfc(model);
-                    success &= true; // redundant
-                    model.setState(STATE_ROLES_LINKED);
+					// normally a CUD state, except for late roles
+					if (isLateRole(model, nextState)) {
+						if (model.getAst() != null)
+							RoleSplitter.linkRoles(model.getAst());
+						model.setState(nextState);
+						model.setMemberState(nextState);
+					} else {
+						success &= ensureUnitState(model, nextState);
+					}
 					break;
 				case STATE_ROLE_INIT_METHODS:
 					success &= establishRoleInitializationMethod(model);
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 93ee0e9..0d37de4 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
@@ -42,7 +42,7 @@
 	public static final int STATE_LENV_CHECK_AND_SET_IMPORTS=   5;//LookupEnvironment & Deps.
 	public static final int STATE_LENV_CONNECT_TYPE_HIERARCHY=  6;//LookupEnvironment & Deps. & CopyInheritance
 	public static final int STATE_LENV_DONE_FIELDS_AND_METHODS= 7;//LookupEnvironment & Deps.
-	public static final int STATE_ROLES_LINKED            =  8;//CopyInheritance & Deps.
+	public static final int STATE_ROLES_LINKED            =  8;//LookupEnvironment & RoleSplitter
 	public static final int STATE_METHODS_PARSED          =  9;//Compiler
 	public static final int STATE_ROLE_INIT_METHODS       = 10;//RoleInitializationMethod
     public static final int STATE_ROLE_FEATURES_COPIED    = 11;//Copy inheritance
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/StateHelper.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/StateHelper.java
index 156f531..02be601 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/StateHelper.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/StateHelper.java
@@ -78,6 +78,7 @@
 		case STATE_LENV_CHECK_AND_SET_IMPORTS:
 		case STATE_LENV_CONNECT_TYPE_HIERARCHY:
 		case STATE_LENV_DONE_FIELDS_AND_METHODS:
+		case STATE_ROLES_LINKED:
 		case STATE_ROLE_FILES_LINKED:
 		case STATE_METHODS_PARSED:
 		case STATE_METHODS_VERIFIED:
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/StateMemento.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/StateMemento.java
index 2e9f3fb..264172e 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/StateMemento.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/control/StateMemento.java
@@ -130,7 +130,7 @@
 		if (state < this._currentlyProcessingState)
 			return false;
 		// FIXME(SH): obsolete after integrating both state models?
-		if (state >= ITranslationStates.STATE_LENV_BUILD_TYPE_HIERARCHY && state <= ITranslationStates.STATE_LENV_DONE_FIELDS_AND_METHODS)
+		if (state >= ITranslationStates.STATE_LENV_BUILD_TYPE_HIERARCHY && state <= ITranslationStates.STATE_ROLES_LINKED)
 			return step < this._completingBindingsStep;
 		return true;
 	}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/statemachine/transformer/RoleSplitter.java b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/statemachine/transformer/RoleSplitter.java
index 18e5aad..5a21efb 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/statemachine/transformer/RoleSplitter.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/objectteams/otdt/internal/core/compiler/statemachine/transformer/RoleSplitter.java
@@ -36,6 +36,7 @@
 import org.eclipse.jdt.internal.codeassist.complete.CompletionOnSingleTypeReference;
 import org.eclipse.jdt.internal.compiler.ast.ASTNode;
 import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
@@ -52,9 +53,9 @@
 import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
 import org.eclipse.objectteams.otdt.internal.core.compiler.control.ITranslationStates;
 import org.eclipse.objectteams.otdt.internal.core.compiler.model.MethodModel;
+import org.eclipse.objectteams.otdt.internal.core.compiler.model.MethodModel.ProblemDetail;
 import org.eclipse.objectteams.otdt.internal.core.compiler.model.RoleModel;
 import org.eclipse.objectteams.otdt.internal.core.compiler.model.TypeModel;
-import org.eclipse.objectteams.otdt.internal.core.compiler.model.MethodModel.ProblemDetail;
 import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstClone;
 import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstConverter;
 import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstEdit;
@@ -397,7 +398,26 @@
 
     }
 
+    /** 
+     * API for LookupEnvironment:
+     * Establish necessary links between ifc- and class-part of each role in the given unit.
+     */
+    public static void linkRoles(CompilationUnitDeclaration unit) {
+		if (unit.types != null)
+			for (TypeDeclaration type : unit.types)
+				linkRoles(type);
+    }
 
+	public static void linkRoles(TypeDeclaration type) {
+		if (type.isRole()) {
+			RoleModel model = type.getRoleModel();
+			RoleSplitter.linkScopes(model);
+			RoleSplitter.linkSuperAndBaseInIfc(model);
+		}
+		if (type.memberTypes != null)
+			for (TypeDeclaration member : type.memberTypes)
+				linkRoles(member);		
+	}
 
 	/**
 	 * After roles have been split and bindings have been completed, transfer
@@ -406,11 +426,12 @@
 	 * Note: tsuper roles are not yet copied. We might need to adjust types from
 	 * super team to current team later (CopyInheritance.TypeLevel.adjustSuperinterfaces).
      */
-	public static void linkSuperAndBaseInIfc(RoleModel role) {
+	private static void linkSuperAndBaseInIfc(RoleModel role) {
 		if (!role.isSourceRole())
 			return; // local type nested in a proper role
-		if (role.getBinding().isBinaryBinding())
-			return; // already linked.
+		ReferenceBinding binding = role.getBinding();
+		if (binding == null || binding.isBinaryBinding())
+			return; // no hope or already linked.
 
 		ReferenceBinding classPart = role.getClassPartBinding();
 		ReferenceBinding ifcPart = role.getInterfacePartBinding();
@@ -451,7 +472,7 @@
 	 * which may appear in interface signatures).
 	 * @param model
 	 */
-	public static void linkScopes(RoleModel model) {
+	private static void linkScopes(RoleModel model) {
 		if (model._classPart != null && model._interfacePart != null) {
 			// may already have error
 			// (we observed duplicateNestedType, nestedHidesEnclosing)
@@ -462,8 +483,6 @@
 		}
 	}
 
-
-
 	public static boolean isClassPartName(char[] typeName) {
 		return CharOperation.prefixEquals(OT_DELIM_NAME, typeName);
 	}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitProblemFinder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitProblemFinder.java
index 458fbda..e596ffc 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitProblemFinder.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitProblemFinder.java
@@ -18,24 +18,26 @@
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.OperationCanceledException;
+
 import org.eclipse.jdt.core.*;
 import org.eclipse.jdt.core.compiler.CategorizedProblem;
 import org.eclipse.jdt.internal.compiler.*;
 import org.eclipse.jdt.internal.compiler.Compiler;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
 import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
 import org.eclipse.jdt.internal.compiler.env.ISourceType;
 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
 import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
 import org.eclipse.jdt.internal.compiler.parser.SourceTypeConverter;
 import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
 import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
+import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
 import org.eclipse.jdt.internal.core.util.CommentRecorderParser;
 import org.eclipse.jdt.internal.core.util.Util;
-import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
-import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
-import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+
 import org.eclipse.objectteams.otdt.internal.core.compiler.control.Config;
 import org.eclipse.objectteams.otdt.internal.core.compiler.control.Dependencies;
 
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyResolver.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyResolver.java
index 5c5175e..32edad8 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyResolver.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyResolver.java
@@ -963,10 +963,6 @@
 		// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=145333)
 		try {
 			this.lookupEnvironment.completeTypeBindings(parsedUnits, hasLocalType, unitsIndex);
-//{ObjectTeams: additional step: this state creates more hierarchy-related links:
-			for (int i=0; i<unitsIndex; i++)
-				Dependencies.ensureState(parsedUnits[i], ITranslationStates.STATE_ROLES_LINKED);
-// SH}
 			// remember type bindings
 			for (int i = 0; i < unitsIndex; i++) {
 				CompilationUnitDeclaration parsedUnit = parsedUnits[i];
diff --git a/testplugins/org.eclipse.objectteams.otdt.tests/model/org/eclipse/objectteams/otdt/tests/otmodel/OTReconcilerTests.java b/testplugins/org.eclipse.objectteams.otdt.tests/model/org/eclipse/objectteams/otdt/tests/otmodel/OTReconcilerTests.java
index 6d2993c..a17f5ec 100644
--- a/testplugins/org.eclipse.objectteams.otdt.tests/model/org/eclipse/objectteams/otdt/tests/otmodel/OTReconcilerTests.java
+++ b/testplugins/org.eclipse.objectteams.otdt.tests/model/org/eclipse/objectteams/otdt/tests/otmodel/OTReconcilerTests.java
@@ -1631,4 +1631,69 @@
     		deleteProject("P");
     	}
     }
+
+    // Bug 400360 - [reconciler] fails to resolve callout-to-field with path-anchored type
+    public void testBug400360() throws CoreException, InterruptedException {
+    	try {
+			// Resources creation
+			IJavaProject p = createOTJavaProject("P", new String[] {""}, new String[] {"JCL15_LIB"}, "bin");
+			IProject project = p.getProject();
+			IProjectDescription prjDesc = project.getDescription();
+			prjDesc.setBuildSpec(OTDTPlugin.createProjectBuildCommands(prjDesc));
+			project.setDescription(prjDesc, null);
+			p.setOption(JavaCore.COMPILER_PB_UNUSED_LOCAL, JavaCore.IGNORE);
+	
+			OTREContainer.initializeOTJProject(project);
+
+			String allShapesSourceString =	
+					"public team class AllShapes {\n" + 
+		    		"\n" + 
+		    		"	public abstract class Connector { }\n" + 
+		    		"	public abstract class RectangularConnector extends Connector { }\n" + 
+		    		"}\n";
+			this.createFile(
+				"/P/AllShapes.java",
+				allShapesSourceString);
+			
+			String chdSourceString =	
+					"public team class CompanyHierarchyDisplay {\n" + 
+		    		"    \n" + 
+		    		"	public final AllShapes _shapes = new AllShapes();\n" + 
+		    		"	\n" + 
+		    		"    public class Connection {\n" + 
+		    		"    	Connector<@_shapes> connShape;\n" + 
+		    		"    }\n" + 
+		    		"}\n";
+			this.createFile(
+				"/P/CompanyHierarchyDisplay.java",
+    			chdSourceString);
+			
+			String versionASourceString =	
+					"public team class VersionA {\n" + 
+		    		"    private final CompanyHierarchyDisplay _chd;\n" + 
+		    		"    \n" + 
+		    		"    public VersionA(CompanyHierarchyDisplay chd) {\n" + 
+		    		"    	_chd = chd;\n" + 
+		    		"    }\n" + 
+		    		"    \n" + 
+		    		"    public class RectangularConnections playedBy Connection<@_chd> {\n" + 
+		    		"       final AllShapes _shapesX = _chd._shapes;\n" +
+		    		"       @SuppressWarnings(\"decapsulation\")\n" + 
+		    		"		void setShape(RectangularConnector<@_shapesX> shape) -> set Connector<@_chd._shapes> connShape;\n" + 
+		    		"    }\n" + 
+		    		"}\n";
+			this.createFile(
+				"/P/VersionA.java",
+				versionASourceString);
+
+			char[] versionASourceChars = versionASourceString.toCharArray();
+			this.problemRequestor.initialize(versionASourceChars);
+			
+			ICompilationUnit unit = getCompilationUnit("/P/VersionA.java").getWorkingCopy(this.wcOwner, null);
+			
+			assertNoProblem(versionASourceChars, unit);
+    	} finally {
+    		deleteProject("P");
+    	}    	
+    }
 }
diff --git a/testplugins/org.eclipse.objectteams.otdt.ui.tests/src/org/eclipse/objectteams/otdt/ui/tests/hierarchy/contentprovider/OTSubHierarchyContentProviderTests.java b/testplugins/org.eclipse.objectteams.otdt.ui.tests/src/org/eclipse/objectteams/otdt/ui/tests/hierarchy/contentprovider/OTSubHierarchyContentProviderTests.java
index 04feb41..cee09fc 100644
--- a/testplugins/org.eclipse.objectteams.otdt.ui.tests/src/org/eclipse/objectteams/otdt/ui/tests/hierarchy/contentprovider/OTSubHierarchyContentProviderTests.java
+++ b/testplugins/org.eclipse.objectteams.otdt.ui.tests/src/org/eclipse/objectteams/otdt/ui/tests/hierarchy/contentprovider/OTSubHierarchyContentProviderTests.java
@@ -61,7 +61,7 @@
     private IType _T8;
     
 	private IType _T1_R1;
-	private IType _T1_R2;
+	// private IType _T1_R2; not within the cone of types reachable from _T1_R1
 	
 	private IType _T2_R1;
 	private IType _T2_R2;
@@ -175,14 +175,7 @@
 					pkg,
 					"T1",
 			        "R1").getCorrespondingJavaElement();
-		
-		_T1_R2 = (IType)
-			getRole(getTestProjectDir(),
-					SRC_FOLDER,
-					pkg,
-					"T1",
-			        "R2").getCorrespondingJavaElement();
-		
+
 		_T2_R1 = (IType)
 			getRole(getTestProjectDir(),
 					SRC_FOLDER,
@@ -272,7 +265,7 @@
 		_allTypesInProject = new IType[]
 		                              {
 		        _T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8,
-		        _T1_R1, _T1_R2, 
+		        _T1_R1, //_T1_R2, 
 		        _T2_R1, _T2_R2,
 		        _T3_R1, _T3_R2,
 		        _T4_R2,
@@ -310,6 +303,7 @@
         for (int idx = 0; idx < _allTypesInProject.length; idx++)
         {
             IType cur = _allTypesInProject[idx];
+            if (cur == _T2_R2) continue; // skip, cur's parent is outside the visible cone
 	        actual = _testObject.getParent(cur);
 	        assertEquals("Unexpected parent for " + cur.getFullyQualifiedName() + " ", parents.get(cur), actual);
         }
diff --git a/testplugins/org.eclipse.objectteams.otdt.ui.tests/src/org/eclipse/objectteams/otdt/ui/tests/hierarchy/contentprovider/TreeNode.java b/testplugins/org.eclipse.objectteams.otdt.ui.tests/src/org/eclipse/objectteams/otdt/ui/tests/hierarchy/contentprovider/TreeNode.java
index 5104585..68d2a43 100644
--- a/testplugins/org.eclipse.objectteams.otdt.ui.tests/src/org/eclipse/objectteams/otdt/ui/tests/hierarchy/contentprovider/TreeNode.java
+++ b/testplugins/org.eclipse.objectteams.otdt.ui.tests/src/org/eclipse/objectteams/otdt/ui/tests/hierarchy/contentprovider/TreeNode.java
@@ -21,7 +21,9 @@
 package org.eclipse.objectteams.otdt.ui.tests.hierarchy.contentprovider;
 
 import java.util.HashMap;
+import java.util.Map.Entry;
 
+import org.eclipse.jdt.core.IJavaElement;
 import org.eclipse.jdt.core.IType;
 import org.eclipse.objectteams.otdt.tests.FileBasedTest;
 
@@ -141,4 +143,19 @@
 		this._children.put(node.getElement(), node);
 		
 	}
+
+	// facilitate debugging:
+	public String toString() {
+		StringBuffer buf = new StringBuffer();
+		toString(buf, 0);
+		return buf.toString();
+	}
+
+	private void toString(StringBuffer buf, int indent) {
+		for (int i=0; i<indent; i++) buf.append(' ');
+		IJavaElement element = (IJavaElement)this._element;
+		buf.append(element.getParent().getElementName()).append('.').append(element.getElementName()).append('\n');
+		for (Entry<Object,TreeNode> e : this._children.entrySet())
+			e.getValue().toString(buf, 4);
+	}
 }
\ No newline at end of file