/******************************************************************************* * Copyright (c) 2000, 2008 IBM Corporation and others. * 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 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.wst.jsdt.internal.corext.dom.fragments; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.wst.jsdt.core.IJavaScriptUnit; import org.eclipse.wst.jsdt.core.JavaScriptModelException; import org.eclipse.wst.jsdt.core.dom.ASTNode; import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit; import org.eclipse.wst.jsdt.core.dom.Expression; import org.eclipse.wst.jsdt.core.dom.InfixExpression; import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite; import org.eclipse.wst.jsdt.internal.corext.SourceRange; import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodeFactory; import org.eclipse.wst.jsdt.internal.corext.dom.GenericVisitor; import org.eclipse.wst.jsdt.internal.corext.dom.JdtASTMatcher; class AssociativeInfixExpressionFragment extends ASTFragment implements IExpressionFragment { private final List/*<Expression>*/ fOperands; private final InfixExpression fGroupRoot; public static IExpressionFragment createSubPartFragmentBySourceRange(InfixExpression node, SourceRange range, IJavaScriptUnit cu) throws JavaScriptModelException { Assert.isNotNull(node); Assert.isNotNull(range); Assert.isTrue(!range.covers(node)); Assert.isTrue(new SourceRange(node).covers(range)); if(!isAssociativeInfix(node)) return null; InfixExpression groupRoot= findGroupRoot(node); Assert.isTrue(isAGroupRoot(groupRoot)); List/*<Expression>*/ groupMembers= AssociativeInfixExpressionFragment.findGroupMembersInOrderFor(groupRoot); List/*<Expression>*/ subGroup= findSubGroupForSourceRange(groupMembers, range); if(subGroup.isEmpty() || rangeIncludesExtraNonWhitespace(range, subGroup, cu)) return null; return new AssociativeInfixExpressionFragment(groupRoot, subGroup); } public static IExpressionFragment createFragmentForFullSubtree(InfixExpression node) { Assert.isNotNull(node); if(!isAssociativeInfix(node)) return null; InfixExpression groupRoot= findGroupRoot(node); Assert.isTrue(isAGroupRoot(groupRoot)); List/*<Expression>*/ groupMembers= AssociativeInfixExpressionFragment.findGroupMembersInOrderFor(node); return new AssociativeInfixExpressionFragment(groupRoot, groupMembers); } private static InfixExpression findGroupRoot(InfixExpression node) { Assert.isTrue(isAssociativeInfix(node)); while(!isAGroupRoot(node)) { ASTNode parent= node.getParent(); Assert.isNotNull(parent); Assert.isTrue(isAssociativeInfix(parent)); Assert.isTrue(((InfixExpression) parent).getOperator() == node.getOperator()); node= (InfixExpression) parent; } return node; } private static List findSubGroupForSourceRange(List/*<Expression>*/ group, SourceRange range) { Assert.isTrue(!group.isEmpty()); List subGroup= new ArrayList(); boolean entered= false, exited= false; if(range.getOffset() == ((ASTNode)group.get(0)).getStartPosition()) entered= true; for(int i= 0; i < group.size() - 1; i++) { ASTNode member= (ASTNode) group.get(i); ASTNode nextMember= (ASTNode) group.get(i + 1); if(entered) { subGroup.add(member); if(rangeEndsBetween(range, member, nextMember)) { exited= true; break; } } else { if(rangeStartsBetween(range, member, nextMember)) entered= true; } } ASTNode lastGroupMember= (ASTNode)group.get(group.size() - 1); if(range.getEndExclusive() == new SourceRange(lastGroupMember).getEndExclusive()) { subGroup.add(lastGroupMember); exited= true; } if(!exited) return new ArrayList(0); return subGroup; } private static boolean rangeStartsBetween(SourceRange range, ASTNode first, ASTNode next) { int pos= range.getOffset(); return first.getStartPosition() + first.getLength() <= pos && pos <= next.getStartPosition(); } private static boolean rangeEndsBetween(SourceRange range, ASTNode first, ASTNode next) { int pos= range.getEndExclusive(); return first.getStartPosition() + first.getLength() <= pos && pos <= next.getStartPosition(); } private static boolean rangeIncludesExtraNonWhitespace(SourceRange range, List/*<Expression>*/ operands, IJavaScriptUnit cu) throws JavaScriptModelException { return Util.rangeIncludesNonWhitespaceOutsideRange(range, getRangeOfOperands(operands), cu.getBuffer()); } private static SourceRange getRangeOfOperands(List/*<Expression>*/ operands) { Expression first= (Expression) operands.get(0); Expression last= (Expression) operands.get(operands.size() - 1); return new SourceRange(first.getStartPosition(), last.getStartPosition() + last.getLength() - first.getStartPosition()); } public IASTFragment[] getMatchingFragmentsWithNode(ASTNode node) { IASTFragment fragmentForNode= ASTFragmentFactory.createFragmentForFullSubtree(node); if (fragmentForNode instanceof AssociativeInfixExpressionFragment) { AssociativeInfixExpressionFragment kin= (AssociativeInfixExpressionFragment)fragmentForNode; return kin.getSubFragmentsWithMyNodeMatching(this); } else { return new IASTFragment[0]; } } /** * Returns List of Lists of <code>ASTNode</code>s */ private static List getMatchingContiguousNodeSubsequences(List source, List toMatch) { //naive implementation: List subsequences= new ArrayList(); for(int i= 0; i < source.size();) { if(matchesAt(i, source, toMatch)) { subsequences.add(source.subList(i, i + toMatch.size())); i += toMatch.size(); } else i++; } return subsequences; } private static boolean matchesAt(int index, List subject, List toMatch) { if(index + toMatch.size() > subject.size()) return false; for(int i= 0; i < toMatch.size(); i++, index++) { if(!JdtASTMatcher.doNodesMatch( (ASTNode) subject.get(index), (ASTNode) toMatch.get(i) ) ) return false; } return true; } private static boolean isAGroupRoot(ASTNode node) { Assert.isNotNull(node); return isAssociativeInfix(node) && !isParentInfixWithSameOperator((InfixExpression) node); } private static boolean isAssociativeInfix(ASTNode node) { return node instanceof InfixExpression && isOperatorAssociative(((InfixExpression)node).getOperator()); } private static boolean isParentInfixWithSameOperator(InfixExpression node) { return node.getParent() instanceof InfixExpression && ((InfixExpression) node.getParent()).getOperator() == node.getOperator(); } private static boolean isOperatorAssociative(InfixExpression.Operator operator) { return operator == InfixExpression.Operator.PLUS || operator == InfixExpression.Operator.TIMES || operator == InfixExpression.Operator.XOR || operator == InfixExpression.Operator.OR || operator == InfixExpression.Operator.AND || operator == InfixExpression.Operator.CONDITIONAL_OR || operator == InfixExpression.Operator.CONDITIONAL_AND; } private AssociativeInfixExpressionFragment(InfixExpression groupRoot, List/*<Expression>*/ operands) { Assert.isTrue(isAGroupRoot(groupRoot)); Assert.isTrue(operands.size() >= 2); fGroupRoot= groupRoot; fOperands= Collections.unmodifiableList(operands); } public boolean matches(IASTFragment other) { if (! other.getClass().equals(getClass())) return false; AssociativeInfixExpressionFragment otherOfKind= (AssociativeInfixExpressionFragment) other; return getOperator() == otherOfKind.getOperator() && doOperandsMatch(otherOfKind); } private boolean doOperandsMatch(AssociativeInfixExpressionFragment other) { if (getOperands().size() != other.getOperands().size()) return false; Iterator myOperands= getOperands().iterator(); Iterator othersOperands= other.getOperands().iterator(); while (myOperands.hasNext() && othersOperands.hasNext()) { ASTNode myOperand= (ASTNode) myOperands.next(); ASTNode othersOperand= (ASTNode) othersOperands.next(); if (! JdtASTMatcher.doNodesMatch(myOperand, othersOperand)) return false; } return true; } public IASTFragment[] getSubFragmentsMatching(IASTFragment toMatch) { return union( getSubFragmentsWithMyNodeMatching(toMatch), getSubFragmentsWithAnotherNodeMatching(toMatch) ); } private IASTFragment[] getSubFragmentsWithMyNodeMatching(IASTFragment toMatch) { if(toMatch.getClass() != getClass()) return new IASTFragment[0]; AssociativeInfixExpressionFragment kinToMatch= (AssociativeInfixExpressionFragment) toMatch; if(kinToMatch.getOperator() != getOperator()) return new IASTFragment[0]; List matchingSubsequences= getMatchingContiguousNodeSubsequences( getOperands(), kinToMatch.getOperands() ); IASTFragment[] matches= new IASTFragment[matchingSubsequences.size()]; for(int i= 0; i < matchingSubsequences.size(); i++) { IASTFragment match= new AssociativeInfixExpressionFragment( fGroupRoot, (List) matchingSubsequences.get(i) ); Assert.isTrue(match.matches(toMatch) || toMatch.matches(match)); matches[i]= match; } return matches; } private IASTFragment[] getSubFragmentsWithAnotherNodeMatching(IASTFragment toMatch) { IASTFragment[] result= new IASTFragment[0]; for (Iterator iter= getOperands().iterator(); iter.hasNext();) { ASTNode operand= (ASTNode) iter.next(); result= union(result, ASTMatchingFragmentFinder.findMatchingFragments(operand, (ASTFragment)toMatch)); } return result; } private static IASTFragment[] union(IASTFragment[] a1, IASTFragment[] a2) { IASTFragment[] union= new IASTFragment[a1.length + a2.length]; System.arraycopy(a1, 0, union, 0, a1.length); System.arraycopy(a2, 0, union, a1.length, a2.length); return union; //TODO: this would be a REAL union...: // ArrayList union= new ArrayList(); // for (int i= 0; i < a1.length; i++) { // union.add(a1[i]); // } // for (int i= 0; i < a2.length; i++) { // if (! union.contains(a2[i])) // union.add(a2[i]); // } // return (IASTFragment[]) union.toArray(new IASTFragment[union.size()]); } /** * Note that this fragment does not directly * represent this expression node, but rather * a part of it. */ public Expression getAssociatedExpression() { return fGroupRoot; } /** * Note that this fragment does not directly * represent this node, but rather a particular sort of * part of its subtree. */ public ASTNode getAssociatedNode() { return fGroupRoot; } public InfixExpression getGroupRoot() { return fGroupRoot; } public int getLength() { return getEndPositionExclusive() - getStartPosition(); } private int getEndPositionExclusive() { List operands= getOperands(); ASTNode lastNode= (ASTNode) operands.get(operands.size() - 1); return lastNode.getStartPosition() + lastNode.getLength(); } public int getStartPosition() { return ((ASTNode) getOperands().get(0)).getStartPosition(); } public List getOperands() { return fOperands; } public InfixExpression.Operator getOperator() { return fGroupRoot.getOperator(); } public Expression createCopyTarget(ASTRewrite rewrite) throws JavaScriptModelException { List allOperands= findGroupMembersInOrderFor(fGroupRoot); if (allOperands.size() == fOperands.size()) { return (Expression) rewrite.createCopyTarget(fGroupRoot); } JavaScriptUnit root= (JavaScriptUnit) fGroupRoot.getRoot(); IJavaScriptUnit cu= (IJavaScriptUnit) root.getJavaElement(); String source= cu.getBuffer().getText(getStartPosition(), getLength()); return (Expression) rewrite.createStringPlaceholder(source, ASTNode.INFIX_EXPRESSION); // //Todo: see whether we could copy bigger chunks of the original selection // // (probably only possible from extendedOperands list or from nested InfixExpressions) // InfixExpression result= rewrite.getAST().newInfixExpression(); // result.setOperator(getOperator()); // Expression first= (Expression) fOperands.get(0); // Expression second= (Expression) fOperands.get(1); // result.setLeftOperand((Expression) rewrite.createCopyTarget(first)); // result.setRightOperand((Expression) rewrite.createCopyTarget(second)); // for (int i= 2; i < fOperands.size(); i++) { // Expression next= (Expression) fOperands.get(i); // result.extendedOperands().add(rewrite.createCopyTarget(next)); // } // return result; } public void replace(ASTRewrite rewrite, ASTNode replacement, TextEditGroup textEditGroup) { List allOperands= findGroupMembersInOrderFor(fGroupRoot); if (allOperands.size() == fOperands.size()) { rewrite.replace(fGroupRoot, replacement, textEditGroup); return; } // Could maybe be done with less edits. // Problem is that the nodes to replace may not be all in the same InfixExpression. int first= allOperands.indexOf(fOperands.get(0)); int after= first + fOperands.size(); ArrayList newOperands= new ArrayList(); for (int i= 0; i < allOperands.size(); i++) { if (i < first || after <= i) { newOperands.add(rewrite.createCopyTarget((Expression) allOperands.get(i))); } else /* i == first */ { newOperands.add(replacement); i= after - 1; } } Expression newExpression= ASTNodeFactory.newInfixExpression(rewrite.getAST(), getOperator(), newOperands); rewrite.replace(getGroupRoot(), newExpression, textEditGroup); } private static ArrayList/*<Expression>*/ findGroupMembersInOrderFor(InfixExpression groupRoot) { return new GroupMemberFinder(groupRoot).fMembersInOrder; } private static class GroupMemberFinder extends GenericVisitor { private ArrayList/*<Expression>*/ fMembersInOrder= new ArrayList(); private InfixExpression fGroupRoot; public GroupMemberFinder(InfixExpression groupRoot) { super(true); Assert.isTrue(isAssociativeInfix(groupRoot)); fGroupRoot= groupRoot; fGroupRoot.accept(this); } protected boolean visitNode(ASTNode node) { if (node instanceof InfixExpression && ((InfixExpression) node).getOperator() == fGroupRoot.getOperator()) return true; fMembersInOrder.add(node); return false; } } public int hashCode() { return fGroupRoot.hashCode(); } public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AssociativeInfixExpressionFragment other= (AssociativeInfixExpressionFragment) obj; return fGroupRoot.equals(other.fGroupRoot) && fOperands.equals(other.fOperands); } }