[465802] Improved breakpoint support in Xtend files

Change-Id: If9a92704752282de1a658d4aa68debb62ed2b624
Signed-off-by: Karsten Thoms <karsten.thoms@itemis.de>
diff --git a/plugins/org.eclipse.xtend.ui/src/org/eclipse/xtend/ui/debug/ExpressionPluginAdapter.java b/plugins/org.eclipse.xtend.ui/src/org/eclipse/xtend/ui/debug/ExpressionPluginAdapter.java
index 7d06f59..075d1b6 100644
--- a/plugins/org.eclipse.xtend.ui/src/org/eclipse/xtend/ui/debug/ExpressionPluginAdapter.java
+++ b/plugins/org.eclipse.xtend.ui/src/org/eclipse/xtend/ui/debug/ExpressionPluginAdapter.java
@@ -15,9 +15,11 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.Stack;
 
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
@@ -34,6 +36,7 @@
 import org.eclipse.internal.xtend.expression.ast.Case;
 import org.eclipse.internal.xtend.expression.ast.Cast;
 import org.eclipse.internal.xtend.expression.ast.ChainExpression;
+import org.eclipse.internal.xtend.expression.ast.CollectionExpression;
 import org.eclipse.internal.xtend.expression.ast.Expression;
 import org.eclipse.internal.xtend.expression.ast.FeatureCall;
 import org.eclipse.internal.xtend.expression.ast.ISyntaxElement;
@@ -120,6 +123,8 @@
 	public IBreakpoint checkBreakpoints(IBreakpoint[] bps, IResource resource, int start, int end, int line) throws CoreException {
 		ISyntaxElement element = findElementForPosition(resource, start, line);
 
+		if(element == null) return null;
+		
 		for (IBreakpoint bp1 : bps) {
 			MWEBreakpoint bp = (MWEBreakpoint) bp1;
 			if (bp.getResource().equals(resource.getFullPath().toString())
@@ -145,158 +150,196 @@
 		} else if (body instanceof LetExpression) {
 			return getContainingElementOfLetExpression((LetExpression) body,
 					position);
+		} else if (body instanceof IfExpression) {
+			return getContainingElementOfIfExpression((IfExpression) body, position);
 		} else
-			return body;
+			return getContainingElement((SyntaxElement)body,position);
 	}
 
-	private boolean contains(final ISyntaxElement elem, final int position) {
-		return elem.getStart() <= position && elem.getEnd() >= position;
+	private boolean containsPosition(final ISyntaxElement elem, final int position) {
+		return elem.getStart() <= position && elem.getEnd() > position;
 	}
-
-	private ISyntaxElement getContainingElement(final SyntaxElement se, final int position) {
-		if (se instanceof ChainExpression)
-			return getContainingElementOfChainExpression((ChainExpression) se, position);
-		else if (se instanceof LetExpression) {
-			return getContainingElementOfLetExpression((LetExpression) se, position);
-		} else if (se instanceof SwitchExpression) {
-			return getContainingElementOfSwitchExpression((SwitchExpression) se, position);
-		} else if (se instanceof ListLiteral) {
-			return getContainingElementOfListLiteral((ListLiteral) se, position);
-		} else if (se instanceof IfExpression) {
-			return getContainingElementOfIfExpression((IfExpression) se, position);
-		}
-
-		if (contains(se, position))
-			return se;
-
-		return null;
-	}
-
-	private ISyntaxElement getContainingElementOfLetExpression(final LetExpression le, int position) {
-		ISyntaxElement result = null;
-
-		// if bp is in "let varname = "varexpr ... we have to shift start and
-		// end.
-		position = shiftPositionIfInside(position, le, le.getVarExpression());
-
-		result = getContainingElement(le.getVarExpression(), position);
-		if (result != null)
-			return result;
-
-		result = getContainingElement(le.getTargetExpression(), position);
-		if (result != null)
-			return result;
-
-		return null;
-	}
-
-	private ISyntaxElement getContainingElementOfIfExpression(final IfExpression ie, int position) {
-		ISyntaxElement result = null;
-
-		// if bp is in "if( expr ) "... we have to shift start and end.
-		position = shiftPositionIfInside(position, ie, ie.getCondition());
-
-		result = getContainingElement(ie.getCondition(), position);
-		if (result != null)
-			return result;
-
-		result = getContainingElement(ie.getThenPart(), position);
-		if (result != null)
-			return result;
-
-		if (ie.getElsePart() != null) {
-			result = getContainingElement(ie.getElsePart(), position);
-			if (result != null)
-				return result;
-		}
-
-		return null;
-	}
-
-	private int shiftPositionIfInside(final int currentPosition,
+	
+	/**
+	 * @since 2.2
+	 */
+	protected int shiftPositionIfInside(final int currentPosition,
 			final SyntaxElement lowerBound, final SyntaxElement upperBound) {
-		return lowerBound.getLine() == upperBound.getLine()
-				&& currentPosition >= lowerBound.getStart()
+		return currentPosition >= lowerBound.getStart()
 				&& currentPosition <= upperBound.getStart() ? upperBound
 				.getStart() : currentPosition;
 	}
 
-	private ISyntaxElement getContainingElementOfListLiteral(final ListLiteral ll, int position) {
-
-		List<Expression> list = ll.getElementsAsList();
-
-		// if bp is in "{   "... we have to shift start and end.
-		if (list.size() > 0) {
-			Expression firstElem = list.get(0);
-			if (ll.getStart() <= position && firstElem.getStart() > position) {
-				position = firstElem.getStart();
-			}
-		}
-
-		for (Expression item : list) {
-			ISyntaxElement result = getContainingElement(item, position);
-			if (result != null)
-				return result;
+	/**
+	 * @since 2.2
+	 */
+	protected ISyntaxElement getContainingElement(final SyntaxElement se, final int position) {
+		if (!containsPosition(se, position)) return null;
+		
+		if ( se instanceof ChainExpression )
+			return getContainingElementOfChainExpression((ChainExpression) se, position);
+		else if ( se instanceof LetExpression ) {
+			return getContainingElementOfLetExpression((LetExpression) se, position);
+		} else if ( se instanceof SwitchExpression ) {
+			return getContainingElementOfSwitchExpression((SwitchExpression) se, position);
+		} else if ( se instanceof ListLiteral ) {
+			return getContainingElementOfListLiteral((ListLiteral) se, position);
+		} else if ( se instanceof IfExpression ) {
+			return getContainingElementOfIfExpression((IfExpression) se, position );
+		} else if ( se instanceof CollectionExpression ) {
+			return getContainingElementOfCollectionExpression((CollectionExpression)se, position);
+		} else if ( se instanceof BooleanOperation ) {
+			return getContainingElementOfBooleanOperation( (BooleanOperation)se, position );
+		} else if ( se instanceof FeatureCall ) {
+			return getContainingElementOfFeatureCall( (FeatureCall)se, position );
 		}
 
 		return null;
 	}
+	
+	/**
+	 * @since 2.2
+	 */
+	protected ISyntaxElement getContainingChild(final SyntaxElement parent, final Collection<SyntaxElement> children, int position) {
+		if(!containsPosition(parent, position)) return null;
+		
+		ISyntaxElement result = null;
+		int newPosition = position;
+		SyntaxElement lastElem = parent;
+		for( SyntaxElement child : children) {
+			if( child == null ) continue;
+			fixBoundaries(child);
+			if( child.getEnd()==0 ) {
+				System.out.println(child.getClass().getSimpleName() + "  " + child.toString());
+			}
+			
+			newPosition = shiftPositionIfInside(newPosition, lastElem, child);
+			
+			result = getContainingElement(child, newPosition);
+			if(result != null) return result;
+			lastElem = child;
+		}
+		
+		return parent;
+	}
+	
+	private ISyntaxElement getContainingElementOfLetExpression(final LetExpression le, int position) {
+		List<SyntaxElement> children = new ArrayList<SyntaxElement>();
+		children.add(le.getVarExpression());
+		children.add(le.getTargetExpression());
+		return getContainingChild(le, children, position);
+	}
+
+	private ISyntaxElement getContainingElementOfIfExpression(final IfExpression ie, int position) {
+		List<SyntaxElement> children = new ArrayList<SyntaxElement>();
+		children.add(ie.getCondition());
+		children.add(ie.getThenPart());
+		children.add(ie.getElsePart());
+		return getContainingChild(ie, children, position);
+	}
+	
+	private ISyntaxElement getContainingElementOfCollectionExpression(final CollectionExpression ce, int position ) {
+		List<SyntaxElement> children = new ArrayList<SyntaxElement>();
+		children.add(ce.getClosure());
+		return getContainingChild(ce, children, position);    
+	}
+	
+	private SyntaxElement fixBoundaries(SyntaxElement e) {
+		if( !( e.getStart() == 0 && e.getEnd() == 0) )
+			return e;
+		
+		if( e instanceof BooleanOperation ) {
+			BooleanOperation be = (BooleanOperation)e;
+			SyntaxElement l = fixBoundaries(be.getLeft());
+			SyntaxElement r = fixBoundaries(be.getRight());
+			be.setLine(l.getLine());
+			be.setStart(l.getStart());
+			be.setEnd(r.getEnd());
+		}
+		
+		return e;
+	}
+	
+	private ISyntaxElement getContainingElementOfBooleanOperation( final BooleanOperation be, int position ) {
+		fixBoundaries(be);
+		
+		List<SyntaxElement> children = new ArrayList<SyntaxElement>();
+		children.add(be.getLeft());
+		children.add(be.getRight());
+		return getContainingChild(be, children, position);
+	}
+	
+	private ISyntaxElement getContainingElementOfFeatureCall( final FeatureCall fe, int position ) {
+		if(!containsPosition(fe,position))
+		  return null;
+		
+		Stack<FeatureCall> fcStack = new Stack<FeatureCall>(); 
+		Expression e = fe;
+		do {
+			FeatureCall fc = (FeatureCall)e;
+			fcStack.push(fc);
+			e = fc.getTarget();
+		} while( e instanceof FeatureCall );
+		
+		FeatureCall fallback = fcStack.peek();
+		
+		while( !fcStack.isEmpty() ) {
+			FeatureCall fc = fcStack.pop();
+			if(fc instanceof OperationCall) {
+				ISyntaxElement result = getContainingElementOfOperationCall((OperationCall)fc, position);
+				if(result != null) 
+					return result;
+			} else if( fc instanceof CollectionExpression) {
+				if(containsPosition(fc, position) ) return getContainingElement(((CollectionExpression)fc).getClosure(),position);
+			}
+		}
+		
+		return fallback;
+	}
+	
+	private ISyntaxElement getContainingElementOfOperationCall( final OperationCall oe, int position ) {
+		if(isFunctionCall(oe)) return oe;
+		List<SyntaxElement> children = new ArrayList<SyntaxElement>(oe.getParamsAsList());
+		return getContainingChild(oe, children, position);
+	}
+	
+	private boolean isFunctionCall(OperationCall op) {
+		char[] name = op.getName().toString().toCharArray();
+		
+		if( name.length == 0 ) return false;
+		
+		for( int i=0; i<name.length; i++ ) {
+			if(i==0) {
+				if(!Character.isJavaIdentifierStart(name[i])) return false;
+			} else{
+				if(!Character.isJavaIdentifierPart(name[i])) return false;
+			}
+		}
+		
+		return true;
+	}
+
+	private ISyntaxElement getContainingElementOfListLiteral(final ListLiteral ll, int position) {
+		List<SyntaxElement> children = new ArrayList<SyntaxElement>(ll.getElementsAsList());
+		return getContainingChild(ll, children, position);
+	}
 
 	private ISyntaxElement getContainingElementOfSwitchExpression(final SwitchExpression se, int position) {
-		ISyntaxElement result = null;
-
-		Expression firstCaseExpression;
-		// if bp is in "switch(expr) { case expr: "... we have to shift start
-		// and end.
-		if (se.getCases().size() > 0)
-			firstCaseExpression = se.getCases().get(0).getThenPart();
-		else
-			firstCaseExpression = se.getDefaultExpr();
-
-		position = shiftPositionIfInside(position, se, firstCaseExpression);
-
+		List<SyntaxElement> children = new ArrayList<SyntaxElement>();
+		children.add(se.getSwitchExpr());
 		for (Case caze : se.getCases()) {
-			result = getContainingElement(caze.getThenPart(), position);
-			if (result != null)
-				return result;
+			children.add(caze.getCondition());
+			children.add(caze.getThenPart());
 		}
-		result = getContainingElement(se.getDefaultExpr(), position);
-		if (result != null)
-			return result;
-
-		return null;
+		children.add(se.getDefaultExpr());
+		return getContainingChild(se, children, position);
 	}
 
 	private ISyntaxElement getContainingElementOfChainExpression(final ChainExpression ce, final int position) {
-
-		ISyntaxElement result = null;
-		result = getRootOperationCall(getContainingElement(ce.getFirst(), position));
-		if (result != null)
-			return result;
-
-		result = getRootOperationCall(getContainingElement(ce.getNext(), position));
-		if (result != null)
-			return result;
-
-		return null;
-	}
-
-	/**
-	 * returns the first operationcall of a call chain ie: for fn3 of
-	 * a.b.fn1().fn2().fn3() this will return fn1().
-	 */
-	private ISyntaxElement getRootOperationCall(final ISyntaxElement element) {
-		ISyntaxElement result = element;
-
-		if (element instanceof OperationCall) {
-			Expression target = ((OperationCall) element).getTarget();
-			while (target != null && target instanceof OperationCall) {
-				result = target;
-				target = ((OperationCall) target).getTarget();
-			}
-		}
-
-		return result;
+		List<SyntaxElement> children = new ArrayList<SyntaxElement>();
+		children.add(ce.getFirst());
+		children.add(ce.getNext());
+		return getContainingChild(ce, children, position); 
 	}
 
 	/**
@@ -307,7 +350,7 @@
 		IXtendXpandResource file = getXtendXpandResource(resource);
 		List<ISyntaxElement> elems = collectFirstLevelElements(file);
 		for (ISyntaxElement elem : elems)
-			if (contains(elem, position))
+			if (containsPosition(elem, position))
 				return elem;
 
 		return null;
@@ -348,8 +391,10 @@
 		return result;
 	}
 
-
-	private ISyntaxElement getBodyOfRootElement(final ISyntaxElement elem) {
+	/**
+	 * @since 2.2
+	 */
+	protected ISyntaxElement getBodyOfRootElement(final ISyntaxElement elem) {
 		if (elem instanceof ExpressionExtensionStatement)
 			return ((ExpressionExtensionStatement) elem).getExpression();
 		else if (elem instanceof CreateExtensionStatement)