Bug 540325: [RJ-Dbg] Improve support for breakpoints in nested functions

Change-Id: Ie6639e59020d157dbef3f2698d54c94af93c5b84
diff --git a/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/Srcref.java b/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/Srcref.java
index 407003f..a0ba0c4 100644
--- a/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/Srcref.java
+++ b/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/Srcref.java
@@ -33,133 +33,174 @@
 	public static final int NA= Integer.MIN_VALUE;
 	
 	
-	public static final int @Nullable [] diff(final int[] base, final int[] rel) {
-		final int[] diff= new int[6];
-		
-		if (base[BEGIN_LINE] > 0 && rel[BEGIN_LINE] > 0) {
-			diff[BEGIN_LINE]= 1 + rel[BEGIN_LINE] - base[BEGIN_LINE];
-			if (diff[BEGIN_LINE] <= 0) {
-				return null;
-			}
-		}
-		else {
-			diff[BEGIN_LINE]= NA;
-		}
-		if (diff[BEGIN_LINE] == 1) {
-			if (base[BEGIN_COLUMN] > 0 && rel[BEGIN_COLUMN] > 0) {
-				diff[BEGIN_COLUMN]= 1 + rel[BEGIN_COLUMN] - base[BEGIN_COLUMN];
-				if (diff[BEGIN_COLUMN] <= 0) {
-					return null;
-				}
-			}
-			else {
-				diff[BEGIN_COLUMN]= NA;
-			}
-			if (base[BEGIN_BYTE] > 0 && rel[BEGIN_BYTE] > 0) {
-				diff[BEGIN_BYTE]= 1 + rel[BEGIN_BYTE] - base[BEGIN_BYTE];
-				if (diff[BEGIN_BYTE] <= 0) {
-					diff[BEGIN_BYTE]= NA;
-				}
-			}
-			else {
-				diff[BEGIN_BYTE]= NA;
-			}
-		}
-		
-		if (base[BEGIN_LINE] > 0 && rel[END_LINE] > 0) {
-			diff[END_LINE]= 1 + rel[END_LINE] - base[BEGIN_LINE];
-			if (diff[END_LINE] <= 0) {
-				return null;
-			}
-		}
-		else {
-			diff[END_LINE]= NA;
-		}
-		if (diff[END_LINE] == 1) {
-			if (base[BEGIN_COLUMN] > 0 && rel[END_COLUMN] > 0) {
-				diff[END_COLUMN]= 1 + rel[END_COLUMN] - base[BEGIN_COLUMN];
-				if (diff[END_COLUMN] <= 0) {
-					return null;
-				}
-			}
-			else {
-				diff[END_COLUMN]= NA;
-			}
-			if (base[BEGIN_BYTE] > 0 && rel[END_BYTE] > 0) {
-				diff[END_BYTE]= 1 + rel[END_BYTE] - base[BEGIN_BYTE];
-				if (diff[END_BYTE] <= 0) {
-					diff[END_BYTE]= NA;
-				}
-			}
-			else {
-				diff[END_BYTE]= NA;
-			}
-		}
-		
-		return diff;
+	public static final int[] copy(final int[] srcref) {
+		final int[] clone= new int[6];
+		System.arraycopy(srcref, 0, clone, 0, 6);
+		return clone;
 	}
 	
-	public static final int addLine(final int base, final int diff) {
-		if (base > 0 && diff > 0) {
-			return base + diff - 1;
+	public static final int addLine(final int main, final int ref) {
+		if (main > 0 && ref > 0) {
+			return main + ref - 1;
 		}
 		else {
 			return NA;
 		}
 	}
 	
-	public static final int[] add(final int[] base, final int[] diff) {
-		final int[] sum= new int[6];
+	public static final int @Nullable [] substract(final int[] main, final int[] ref) {
+		final int[] result= new int[6];
 		
-		sum[BEGIN_LINE]= addLine(base[BEGIN_LINE], diff[BEGIN_LINE]);
-		if (diff[BEGIN_LINE] == 1) {
-			if (base[BEGIN_COLUMN] > 0 && diff[BEGIN_COLUMN] > 0) {
-				sum[BEGIN_COLUMN]= base[BEGIN_COLUMN] + diff[BEGIN_COLUMN] - 1;
-			}
-			else {
-				sum[BEGIN_COLUMN]= NA;
-			}
-			if (base[BEGIN_BYTE] > 0 && diff[BEGIN_BYTE] > 0) {
-				sum[BEGIN_BYTE]= base[BEGIN_BYTE] + diff[BEGIN_BYTE] - 1;
-			}
-			else {
-				sum[BEGIN_BYTE]= NA;
+		if (main[BEGIN_LINE] > 0 && ref[BEGIN_LINE] > 0) {
+			result[BEGIN_LINE]= 1 + main[BEGIN_LINE] - ref[BEGIN_LINE];
+			if (result[BEGIN_LINE] <= 0) {
+				return null;
 			}
 		}
 		else {
-			sum[BEGIN_COLUMN]= base[BEGIN_COLUMN];
-			sum[BEGIN_BYTE]= base[BEGIN_BYTE];
+			result[BEGIN_LINE]= NA;
 		}
-		
-		sum[END_LINE]= addLine(base[BEGIN_LINE], diff[END_LINE]);
-		if (diff[END_LINE] == 1) {
-			if (base[BEGIN_COLUMN] > 0 && diff[END_COLUMN] > 0) {
-				sum[END_COLUMN]= base[BEGIN_COLUMN] + diff[END_COLUMN] - 1;
+		if (result[BEGIN_LINE] == 1) {
+			if (main[BEGIN_COLUMN] > 0 && ref[BEGIN_COLUMN] > 0) {
+				result[BEGIN_COLUMN]= 1 + main[BEGIN_COLUMN] - ref[BEGIN_COLUMN];
+				if (result[BEGIN_COLUMN] <= 0) {
+					return null;
+				}
 			}
 			else {
-				sum[END_COLUMN]= NA;
+				result[BEGIN_COLUMN]= NA;
 			}
-			if (base[BEGIN_BYTE] > 0 && diff[END_BYTE] > 0) {
-				sum[END_BYTE]= base[BEGIN_BYTE] + diff[END_BYTE] - 1;
+			if (main[BEGIN_BYTE] > 0 && ref[BEGIN_BYTE] > 0) {
+				result[BEGIN_BYTE]= 1 + main[BEGIN_BYTE] - ref[BEGIN_BYTE];
+				if (result[BEGIN_BYTE] <= 0) {
+					result[BEGIN_BYTE]= NA;
+				}
 			}
 			else {
-				sum[END_BYTE]= NA;
+				result[BEGIN_BYTE]= NA;
 			}
 		}
 		else {
-			sum[END_COLUMN]= base[END_COLUMN];
-			sum[END_BYTE]= base[END_BYTE];
+			result[BEGIN_COLUMN]= main[BEGIN_COLUMN];
+			result[BEGIN_BYTE]= NA;
 		}
 		
-		return sum;
+		if (main[END_LINE] > 0 && ref[BEGIN_LINE] > 0) {
+			result[END_LINE]= 1 + main[END_LINE] - ref[BEGIN_LINE];
+			if (result[END_LINE] <= 0) {
+				return null;
+			}
+		}
+		else {
+			result[END_LINE]= NA;
+		}
+		if (result[END_LINE] == 1) {
+			if (main[END_COLUMN] > 0 && ref[BEGIN_COLUMN] > 0) {
+				result[END_COLUMN]= 1 + main[END_COLUMN] - ref[BEGIN_COLUMN];
+				if (result[END_COLUMN] <= 0) {
+					return null;
+				}
+			}
+			else {
+				result[END_COLUMN]= NA;
+			}
+			if (main[END_BYTE] > 0 && ref[BEGIN_BYTE] > 0) {
+				result[END_BYTE]= 1 + main[END_BYTE] - ref[BEGIN_BYTE];
+				if (result[END_BYTE] <= 0) {
+					result[END_BYTE]= NA;
+				}
+			}
+			else {
+				result[END_BYTE]= NA;
+			}
+		}
+		else {
+			result[END_COLUMN]= main[END_COLUMN];
+			result[END_BYTE]= NA;
+		}
+		
+		return result;
 	}
 	
+	public static final int[] add(final int[] main, final int[] ref) {
+		final int[] result= new int[6];
+		
+		result[BEGIN_LINE]= addLine(main[BEGIN_LINE], ref[BEGIN_LINE]);
+		if (main[BEGIN_LINE] == 1) {
+			if (main[BEGIN_COLUMN] > 0 && ref[BEGIN_COLUMN] > 0) {
+				result[BEGIN_COLUMN]= main[BEGIN_COLUMN] + ref[BEGIN_COLUMN] - 1;
+			}
+			else {
+				result[BEGIN_COLUMN]= NA;
+			}
+			if (main[BEGIN_BYTE] > 0 && ref[BEGIN_BYTE] > 0) {
+				result[BEGIN_BYTE]= main[BEGIN_BYTE] + ref[BEGIN_BYTE] - 1;
+			}
+			else {
+				result[BEGIN_BYTE]= NA;
+			}
+		}
+		else {
+			result[BEGIN_COLUMN]= main[BEGIN_COLUMN];
+			result[BEGIN_BYTE]= main[BEGIN_BYTE];
+		}
+		
+		result[END_LINE]= addLine(main[END_LINE], ref[BEGIN_LINE]);
+		if (main[END_LINE] == 1) {
+			if (main[BEGIN_COLUMN] > 0 && ref[END_COLUMN] > 0) {
+				result[END_COLUMN]= main[BEGIN_COLUMN] + ref[END_COLUMN] - 1;
+			}
+			else {
+				result[END_COLUMN]= NA;
+			}
+			if (main[BEGIN_BYTE] > 0 && ref[END_BYTE] > 0) {
+				result[END_BYTE]= main[BEGIN_BYTE] + ref[END_BYTE] - 1;
+			}
+			else {
+				result[END_BYTE]= NA;
+			}
+		}
+		else {
+			result[END_COLUMN]= main[END_COLUMN];
+			result[END_BYTE]= main[END_BYTE];
+		}
+		
+		return result;
+	}
+	
+	
+	public static final int[] merge(final int[] main, final int[] last) {
+		final int[] result= copy(main);
+		if (last != main && last[END_LINE] > 0) {
+			if (last[END_LINE] == result[END_LINE]) {
+				if (last[END_COLUMN] > 0 && last[END_COLUMN] > result[END_COLUMN]) {
+					result[END_COLUMN]= last[END_COLUMN];
+					result[END_BYTE]= last[END_BYTE];
+				}
+			}
+			else if (last[END_LINE] > result[END_LINE]) {
+				result[END_LINE]= last[END_LINE];
+				result[END_COLUMN]= last[END_COLUMN];
+				result[END_BYTE]= last[END_BYTE];
+			}
+		}
+		return result;
+	}
 	
 	public static final boolean equalsStart(final int[] srcref1, final int[] srcref2) {
 		return (srcref1[BEGIN_LINE] == srcref2[BEGIN_LINE]
 				&& srcref1[BEGIN_COLUMN] == srcref2[BEGIN_COLUMN]);
 	}
 	
+	public static final boolean contains(final int[] srcref1, final int[] srcref2) {
+		return (((srcref1[BEGIN_LINE] == srcref2[BEGIN_LINE]) ?
+						(srcref1[BEGIN_COLUMN] <= srcref2[BEGIN_COLUMN]) :
+						(srcref1[BEGIN_LINE] < srcref2[BEGIN_LINE]) )
+				&& ((srcref1[END_LINE] == srcref2[END_LINE]) ?
+						(srcref1[END_COLUMN] >= srcref2[END_COLUMN]) :
+						(srcref1[END_LINE] > srcref2[END_LINE]) ) );
+	}
+	
 	public static final boolean equalsDim(final int[] srcref1, final int[] srcref2) {
 		return (srcref1[END_LINE] - srcref1[BEGIN_LINE] == srcref2[Srcref.END_LINE] - srcref2[BEGIN_LINE]
 				&& srcref1[END_COLUMN] - srcref1[BEGIN_COLUMN] == srcref2[Srcref.END_COLUMN] - srcref2[BEGIN_COLUMN]);
diff --git a/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/TracepointPosition.java b/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/TracepointPosition.java
index 773d685..d2f9a55 100644
--- a/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/TracepointPosition.java
+++ b/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/TracepointPosition.java
@@ -35,23 +35,35 @@
 	
 	private final long id;
 	
-	protected int[] exprIndex;
-	protected int @Nullable [] exprSrcref;
+	private final int[] exprIndex;
+	private final int[] @Nullable [] exprSrcrefs;
 	
 	
 	public TracepointPosition(final int type, final long id,
+			final int[] index) {
+		this.type= type;
+		this.id= id;
+		this.exprIndex= nonNullAssert(index);
+		this.exprSrcrefs= new int[this.exprIndex.length] @Nullable [];
+	}
+	
+	public TracepointPosition(final int type, final long id,
 			final int[] index, final int @Nullable [] srcref) {
 		this.type= type;
 		this.id= id;
 		this.exprIndex= nonNullAssert(index);
-		this.exprSrcref= srcref;
+		this.exprSrcrefs= new int[this.exprIndex.length] @Nullable [];
+		this.exprSrcrefs[this.exprSrcrefs.length - 1]= srcref;
 	}
 	
 	TracepointPosition(final RJIO io) throws IOException {
 		this.type= io.readInt();
 		this.id= io.readLong();
-		this.exprIndex= io.readIntArray();
-		this.exprSrcref= io.readIntArray();
+		this.exprIndex= nonNullAssert(io.readIntArray());
+		this.exprSrcrefs= new int[this.exprIndex.length] @Nullable [];
+		for (int i= 0; i < this.exprSrcrefs.length; i++) {
+			this.exprSrcrefs[i]= io.readIntArray();
+		}
 	}
 	
 	@Override
@@ -59,7 +71,10 @@
 		io.writeInt(this.type);
 		io.writeLong(this.id);
 		io.writeIntArray(this.exprIndex, this.exprIndex.length);
-		io.writeIntArray(this.exprSrcref, (this.exprSrcref != null) ? 6 : -1);
+		for (int i= 0; i < this.exprSrcrefs.length; i++) {
+			final int[] srcref= this.exprSrcrefs[i];
+			io.writeIntArray(srcref, (srcref != null) ? 6 : -1);
+		}
 	}
 	
 	
@@ -77,8 +92,16 @@
 		return this.exprIndex;
 	}
 	
+	public int [] @Nullable [] getSrcrefs() {
+		return this.exprSrcrefs;
+	}
+	
+	public int @Nullable [] getSrcref(final int level) {
+		return this.exprSrcrefs[level];
+	}
+	
 	public int @Nullable [] getSrcref() {
-		return this.exprSrcref;
+		return this.exprSrcrefs[this.exprSrcrefs.length - 1];
 	}
 	
 	
@@ -129,7 +152,7 @@
 		final StringBuilder sb= new StringBuilder("TracepointPosition"); //$NON-NLS-1$
 		sb.append(" (type= ").append(this.type).append(")"); //$NON-NLS-1$ //$NON-NLS-2$
 		sb.append("\n\t" + "exprIndex= ").append(Arrays.toString(this.exprIndex)); //$NON-NLS-1$ //$NON-NLS-2$
-		sb.append("\n\t" + "exprSrcref= ").append(Arrays.toString(this.exprSrcref)); //$NON-NLS-1$ //$NON-NLS-2$
+		sb.append("\n\t" + "exprSrcref= ").append(Arrays.toString(getSrcref())); //$NON-NLS-1$ //$NON-NLS-2$
 		return sb.toString();
 	}
 	
diff --git a/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/Tracepoints.java b/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/Tracepoints.java
index 15cf5e5..ae98da9 100644
--- a/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/Tracepoints.java
+++ b/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/dbg/Tracepoints.java
@@ -14,7 +14,10 @@
 
 package org.eclipse.statet.rj.server.dbg;
 
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -23,6 +26,7 @@
 import org.eclipse.statet.jcommons.collections.ImCollections;
 import org.eclipse.statet.jcommons.lang.NonNull;
 import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
 
 
 @NonNullByDefault
@@ -45,6 +49,16 @@
 		return -1;
 	}
 	
+	public static final @Nullable TracepointPosition getPositionById(final List<? extends TracepointPosition> list,
+			final long id) {
+		for (int j= 0; j < list.size(); j++) {
+			final TracepointPosition p= list.get(j);
+			if (p.getId() == id) {
+				return p;
+			}
+		}
+		return null;
+	}
 	
 	public static final boolean containsPositionsById(final List<? extends TracepointPosition> list,
 			final List<? extends TracepointPosition> requiredIds) {
@@ -99,7 +113,7 @@
 		return true;
 	}
 	
-	public static final List<TracepointPosition> filterPositionsById(final List<? extends TracepointPosition> list,
+	public static final List<TracepointPosition> filterPositionsByIdRequired(final List<? extends TracepointPosition> list,
 			final List<? extends TracepointPosition> requiredIds) {
 		final int l= requiredIds.size();
 		if (list.size() == l) {
@@ -120,7 +134,7 @@
 		return ImCollections.newList(array);
 	}
 	
-	public static final List<TracepointPosition> intersectPositionsById(final List<? extends TracepointPosition> list,
+	public static final List<TracepointPosition> filterPositionsById(final List<? extends TracepointPosition> list,
 			final List<? extends TracepointPosition> includedIds) {
 		final int l= includedIds.size();
 		final List<TracepointPosition> intersection= new ArrayList<>(l);
@@ -139,7 +153,7 @@
 		return intersection;
 	}
 	
-	public static final List<TracepointPosition> intersectPositionsById(final List<? extends TracepointPosition> list,
+	public static final List<TracepointPosition> filterPositionsById(final List<? extends TracepointPosition> list,
 			final long[] includedIds) {
 		final int l= includedIds.length;
 		final List<TracepointPosition> intersection= new ArrayList<>(l);
@@ -158,6 +172,95 @@
 		return intersection;
 	}
 	
+	public static final List<TracepointPosition> filterPositionsBySrcref(final List<? extends TracepointPosition> list,
+			final int[] baseSrcref, final int[] includedSrcref) {
+		final List<TracepointPosition> intersection= new ArrayList<>(list.size());
+		for (int j= 0; j < list.size(); j++) {
+			final TracepointPosition p= list.get(j);
+			final int [] @Nullable [] srcrefs= p.getSrcrefs();
+			for (int i= 0; i < srcrefs.length; i++) {
+				if (srcrefs[i] != null
+						&& Srcref.equalsStart(Srcref.add(srcrefs[i], baseSrcref), includedSrcref) ) {
+					intersection.add(p);
+					break;
+				}
+			}
+		}
+		return intersection;
+	}
+	
+	public static final int @Nullable [] findIndexBySrcref(final List<? extends TracepointPosition> list,
+			final int[] baseSrcref, final int[] includedSrcref) {
+		for (int j= 0; j < list.size(); j++) {
+			final TracepointPosition p= list.get(j);
+			final int [] @Nullable [] srcrefs= p.getSrcrefs();
+			for (int depth= 0; depth < srcrefs.length; depth++) {
+				if (srcrefs[depth] != null
+						&& Srcref.equalsStart(Srcref.add(srcrefs[depth], baseSrcref), includedSrcref) ) {
+					return Arrays.copyOf(p.getIndex(), depth + 1);
+				}
+			}
+		}
+		return null;
+	}
+	
+	public static final int @Nullable [] findIndexByReference(final List<? extends TracepointPosition> list,
+			final List<? extends TracepointPosition> reference) {
+		if (!list.isEmpty()) {
+			final TracepointPosition p= list.get(0);
+			final TracepointPosition refP= nonNullAssert(getPositionById(reference, p.getId()));
+			final int[] index= p.getIndex();
+			final int depth= index.length - 1 - refP.getIndex().length;
+			if (depth > 0 && depth < index.length) {
+				return Arrays.copyOf(index, depth + 1);
+			}
+		}
+		return null;
+	}
+	
+	private static final boolean equalsIndex(final int[] index, final int[] included) {
+		if (index.length < included.length) {
+			return false;
+		}
+		for (int i= 0; i < included.length; i++) {
+			if (index[i] != included[i]) {
+				return false;
+			}
+		}
+		return true;
+	}
+	
+	public static final int @Nullable [] getSrcrefForIndex(final List<? extends TracepointPosition> list,
+			final int[] includedIndex) {
+		final int depth= includedIndex.length - 1;
+		for (int j= 0; j < list.size(); j++) {
+			final TracepointPosition p= list.get(j);
+			final int[] srcref;
+			if (equalsIndex(p.getIndex(), includedIndex)
+					&& (srcref= p.getSrcref(depth)) != null ) {
+				return srcref;
+			}
+		}
+		return null;
+	}
+	
+	public static final List<TracepointPosition> rebasePositionsByIndex(final List<? extends TracepointPosition> list,
+			final int[] newBaseIndex) {
+		final int depth= newBaseIndex.length - 1;
+		final List<TracepointPosition> intersection= new ArrayList<>(list.size());
+		for (int j= 0; j < list.size(); j++) {
+			final TracepointPosition p= list.get(j);
+			final int[] index= p.getIndex();
+			if (equalsIndex(index, newBaseIndex)
+					&& p.getSrcref() != null && p.getSrcref(depth) != null ) {
+				intersection.add(new TracepointPosition(p.getType(), p.getId(),
+					Arrays.copyOfRange(index, depth + 1, index.length),
+					Srcref.substract(p.getSrcref(), p.getSrcref(depth)) ));
+			}
+		}
+		return intersection;
+	}
+	
 	
 	private static final char[] DIGITS= new char[] {
 			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
diff --git a/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/rh/AbstractTracepointManager.java b/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/rh/AbstractTracepointManager.java
index 0f573de..4bf7d39 100644
--- a/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/rh/AbstractTracepointManager.java
+++ b/core/org.eclipse.statet.rj.server/src/org/eclipse/statet/rj/server/rh/AbstractTracepointManager.java
@@ -14,13 +14,18 @@
 
 package org.eclipse.statet.rj.server.rh;
 
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
 import static org.eclipse.statet.rj.server.dbg.Srcfiles.equalsTimestamp;
 import static org.eclipse.statet.rj.server.dbg.Tracepoints.LOGGER;
 import static org.eclipse.statet.rj.server.dbg.Tracepoints.containsPositionsById;
 import static org.eclipse.statet.rj.server.dbg.Tracepoints.equalsPositions;
 import static org.eclipse.statet.rj.server.dbg.Tracepoints.filterPositionsById;
+import static org.eclipse.statet.rj.server.dbg.Tracepoints.filterPositionsByIdRequired;
+import static org.eclipse.statet.rj.server.dbg.Tracepoints.findIndexByReference;
+import static org.eclipse.statet.rj.server.dbg.Tracepoints.findIndexBySrcref;
+import static org.eclipse.statet.rj.server.dbg.Tracepoints.getSrcrefForIndex;
 import static org.eclipse.statet.rj.server.dbg.Tracepoints.indexOfTracepointById;
-import static org.eclipse.statet.rj.server.dbg.Tracepoints.intersectPositionsById;
+import static org.eclipse.statet.rj.server.dbg.Tracepoints.rebasePositionsByIndex;
 import static org.eclipse.statet.rj.server.rh.ObjectManager.WEAK_REF;
 
 import java.util.ArrayList;
@@ -67,6 +72,7 @@
 	private static final String ENV_INSTALLED_TAG= "#" + WEAK_REF + ":dbg.tracepoints";
 	
 	
+	private static final int MAX_HISTORY= 12;
 	private static final int MAX_FUZZY= 8;
 	
 	
@@ -171,6 +177,8 @@
 	protected static final ImList<TracepointPosition> NO_POSITIONS= ImCollections.emptyList();
 	protected static final ElementTracepoints NO_POSITION_ELEMENT_TRACEPOINTS= new ElementTracepoints(
 			new SrcfileData(null, null, 0), "", null, NO_POSITIONS ); //$NON-NLS-1$
+	protected static final ElementTracepoints NO_CHANGE_ELEMENT_TRACEPOINTS= new ElementTracepoints(
+			new SrcfileData(null, null, 0), "", null, NO_POSITIONS ); //$NON-NLS-1$
 	
 	
 	/**
@@ -224,7 +232,7 @@
 		}
 		
 		public List<? extends TracepointPosition> getActualPositions(final List<? extends TracepointPosition> positions) {
-			return filterPositionsById(positions, this.current.getPositions());
+			return filterPositionsByIdRequired(positions, this.current.getPositions());
 		}
 		
 		public long getRequestedTimestamp() {
@@ -337,6 +345,7 @@
 			}
 			
 			ElementEntry elementEntry= null;
+			boolean isNestedMatch= false;
 			if (id != null) { // by id
 				elementEntry= getElementEntry(id);
 			}
@@ -348,9 +357,16 @@
 						}
 						final ElementTracepoints cand= entry.getTracepoints(timestamp);
 						if (cand != null && cand.getElementSrcref() != null
-								&& Srcref.equalsStart(cand.getElementSrcref(), srcref)) {
-							elementEntry= entry;
-							break;
+								&& Srcref.contains(cand.getElementSrcref(), srcref) ) {
+							if (Srcref.equalsStart(cand.getElementSrcref(), srcref)) {
+								elementEntry= entry;
+								isNestedMatch= false;
+								break;
+							}
+							else {
+								elementEntry= entry;
+								isNestedMatch= true;
+							}
 						}
 					}
 				}
@@ -391,7 +407,9 @@
 				}
 			}
 			return (elementEntry != null) ?
-					elementEntry.findCurrentTracepoints(timestamp, srcref, this.states) :
+					(isNestedMatch) ?
+							elementEntry.findCurrentNestedTracepoints(timestamp, srcref, this.states) :
+							elementEntry.findCurrentTracepoints(timestamp, srcref, this.states) :
 					null;
 		}
 		
@@ -496,7 +514,7 @@
 				this.history.set(0, tracepoints);
 			}
 			else {
-				if (this.history.size() >= 10) {
+				if (this.history.size() >= MAX_HISTORY) {
 					this.history.remove(this.history.size() - 1);
 				}
 				this.history.add(0, tracepoints);
@@ -553,7 +571,7 @@
 										return cand;
 									}
 									best= cand;
-									timestampPositions= intersectPositionsById(cand.getPositions(), current.getPositions());
+									timestampPositions= filterPositionsById(cand.getPositions(), current.getPositions());
 									end= i;
 									break;
 								}
@@ -593,7 +611,7 @@
 					}
 					if (end > 1 && !current.getPositions().isEmpty()) {
 						if (timestamp != 0) {
-							List<TracepointPosition> timestampPositions= null;
+							List<TracepointPosition> timestampActualPositions= null;
 							for (int i= 0; i < end; i++) {
 								final ElementTracepoints cand= this.history.get(i);
 								if (equalsTimestamp(cand.getSrcfile().getTimestamp(), timestamp)) {
@@ -601,17 +619,17 @@
 										return new ElementTracepointsCollection(this.updateStamp,
 												current, cand, timestamp );
 									}
-									timestampPositions= intersectPositionsById(cand.getPositions(), current.getPositions());
+									timestampActualPositions= filterPositionsById(cand.getPositions(), current.getPositions());
 									end= i;
 									break;
 								}
 							}
-							if (timestampPositions != null && !timestampPositions.isEmpty() ) {
+							if (timestampActualPositions != null && !timestampActualPositions.isEmpty()) {
 								for (int i= 0; i < end; i++) {
 									final ElementTracepoints cand= this.history.get(i);
 									if (containsPositionsById(cand.getPositions(), current.getPositions())
-											&& equalsPositions(cand.getPositions(), timestampPositions,
-														timestampPositions )) {
+											&& equalsPositions(cand.getPositions(), timestampActualPositions,
+														timestampActualPositions )) {
 										return new ElementTracepointsCollection(this.updateStamp,
 												current, cand, timestamp );
 									}
@@ -643,6 +661,110 @@
 			}
 		}
 		
+		public @Nullable ElementTracepointsCollection findCurrentNestedTracepoints(
+				final long timestamp, final int[] srcref,
+				final @Nullable StateSet states) {
+			synchronized (this.history) {
+				if (!this.history.isEmpty()) {
+					final ElementTracepoints current= this.history.get(0);
+					int end= this.history.size();
+					if (current.getPositions().isEmpty()
+							|| (states != null && isAllDeleted(current.getPositions(), states)) ) {
+						return createNoPositionCollection();
+					}
+					if (timestamp != 0) {
+						int[] timestampSrcref= null;
+						List<TracepointPosition> timestampPositions= null;
+						for (int i= 0; i < end; i++) {
+							final ElementTracepoints cand= this.history.get(i);
+							if (equalsTimestamp(cand.getSrcfile().getTimestamp(), timestamp)) {
+								if (cand.getElementSrcref() == null) {
+									break;
+								}
+								final int[] index= findIndexBySrcref(cand.getPositions(),
+										nonNullAssert(cand.getElementSrcref()), srcref );
+								if (index != null) {
+									timestampSrcref= nonNullAssert(getSrcrefForIndex(
+											cand.getPositions(), index ));
+									timestampPositions= rebasePositionsByIndex(
+											cand.getPositions(), index );
+									if (containsPositionsById(cand.getPositions(), current.getPositions())) {
+										return createNestedCollection(current,
+												createNested(cand, timestampSrcref, filterPositionsById(
+														timestampPositions, current.getPositions() )),
+												timestamp, states );
+									}
+								}
+								end= i;
+								break;
+							}
+						}
+						if (timestampPositions != null && !timestampPositions.isEmpty()) {
+							for (int i= 0; i < end; i++) {
+								final ElementTracepoints cand= this.history.get(i);
+								if (containsPositionsById(cand.getPositions(), current.getPositions())) {
+									final List<TracepointPosition> compareList= filterPositionsById(
+											cand.getPositions(), timestampPositions );
+									final int[] index= findIndexByReference(compareList, timestampPositions);
+									if (index != null) {
+										final int[] candSrcref= nonNullAssert(getSrcrefForIndex(
+												compareList, index ));
+										final List<TracepointPosition> candPositions= rebasePositionsByIndex(
+												cand.getPositions(), index );
+										if (equalsPositions(candPositions, timestampPositions,
+												timestampPositions )) {
+											return createNestedCollection(current,
+													createNested(cand, candSrcref, filterPositionsById(
+															candPositions, current.getPositions() )),
+													timestamp, states );
+										}
+									}
+								}
+							}
+							{	final List<TracepointPosition> positions= filterPositionsById(
+										timestampPositions, current.getPositions() );
+								if (positions.isEmpty()
+										|| (states != null && isAllDeleted(positions, states)) ) {
+									return createNoPositionCollection();
+								}
+							}
+						}
+						return createNoChangeCollection();
+					}
+				}
+				return null;
+			}
+		}
+		
+		private ElementTracepointsCollection createNoPositionCollection() {
+			return new ElementTracepointsCollection(this.updateStamp,
+					NO_POSITION_ELEMENT_TRACEPOINTS, NO_POSITION_ELEMENT_TRACEPOINTS, 0 );
+		}
+		
+		private ElementTracepointsCollection createNoChangeCollection() {
+			return new ElementTracepointsCollection(this.updateStamp,
+					NO_CHANGE_ELEMENT_TRACEPOINTS, NO_CHANGE_ELEMENT_TRACEPOINTS, 0 );
+		}
+		
+		private ElementTracepoints createNested(final ElementTracepoints cand,
+				final int[] srcref, final List<TracepointPosition> nestedPositions) {
+			return new ElementTracepoints(cand.getSrcfile(), this.id, srcref, nestedPositions);
+		}
+		
+		private ElementTracepointsCollection createNestedCollection(final ElementTracepoints current,
+				final ElementTracepoints nested, final long requestedTimestamp,
+				final @Nullable StateSet states) {
+			final List<TracepointPosition> currentPositions= filterPositionsById(
+					current.getPositions(), nested.getPositions());
+			if (currentPositions.isEmpty()
+					|| (states != null && isAllDeleted(currentPositions, states)) ) {
+				return createNoPositionCollection();
+			}
+			return new ElementTracepointsCollection(this.updateStamp,
+					new ElementTracepoints(current.getSrcfile(), this.id, null, currentPositions),
+					nested, requestedTimestamp );
+		}
+		
 	}
 	
 	protected class StateEntry implements Comparable<StateEntry> {