/*******************************************************************************
* Copyright (c) 2009 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
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.ui.corext.dom.fragments;
import org.eclipse.core.runtime.Assert;
import org.eclipse.dltk.core.ISourceRange;
import org.eclipse.dltk.core.SourceRange;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.php.core.ast.nodes.ASTNode;
import org.eclipse.php.core.ast.nodes.Expression;
import org.eclipse.php.core.ast.nodes.InfixExpression;
import org.eclipse.php.core.ast.visitor.HierarchicalVisitor;
import org.eclipse.php.internal.core.corext.dom.Selection;
import org.eclipse.php.internal.core.corext.dom.SelectionAnalyzer;
/**
* Creates various differing kinds of IASTFragments, all through a very narrow
* interface. The kind of IASTFragment produced will depend on properties of the
* parameters supplied to the factory methods, such as the types and
* characteristics of AST nodes, or the location of source ranges.
*
* In general, the client will not be aware of exactly what kind of fragment is
* obtained from these methods. Beyond the functionality provided by the
* IASTFragment interface, the client can know, however, based on the parameters
* passed, some things about the created fragment. See the documentation of the
* factory methods.
*
* @see IASTFragment
*
*/
public class ASTFragmentFactory {
/**
* If possible, this method creates a fragment whose source code range is
* <code>range</code> within compilation unit <code>cu</code>, and which
* resides somewhere within the subtree identified by <code>scope</code>.
*
* XXX: more doc (current assertions about input vs. output)
*
* @param range
* The source range which the create fragment must have.
* @param scope
* A node identifying the AST subtree in which the fragment must
* lie.
* @param cu
* The compilation unit to which the source range applies, and to
* which the AST corresponds.
* @return IASTFragment A fragment whose source range is <code>range</code>
* within compilation unit <code>cu</code>, residing somewhere
* within the AST subtree identified by <code>scope</code>.
* @throws JavaModelException
*/
public static IASTFragment createFragmentForSourceRange(SourceRange range, ASTNode scope, IDocument document)
throws Exception {
SelectionAnalyzer sa = new SelectionAnalyzer(
Selection.createFromStartLength(range.getOffset(), range.getLength()), false);
scope.accept(sa);
if (isSingleNodeSelected(sa, range, document, scope))
return ASTFragmentFactory.createFragmentForFullSubtree(sa.getFirstSelectedNode(), null);
if (isEmptySelectionCoveredByANode(range, sa))
return ASTFragmentFactory.createFragmentForFullSubtree(sa.getLastCoveringNode(), null);
return ASTFragmentFactory.createFragmentForSubPartBySourceRange(sa.getLastCoveringNode(), range, scope,
document);
}
/**
* Returns <code>null</code> if the indices, taken with respect to the node,
* do not correspond to a valid node-sub-part fragment.
*
* @param document
* @throws Exception
*/
private static IASTFragment createFragmentForSubPartBySourceRange(ASTNode node, ISourceRange range, ASTNode scope,
IDocument document) throws Exception {
return FragmentForSubPartBySourceRangeFactory.createFragmentFor(node, range, scope, document);
}
private static boolean isEmptySelectionCoveredByANode(ISourceRange range, SelectionAnalyzer sa) {
return range.getLength() == 0 && sa.getFirstSelectedNode() == null && sa.getLastCoveringNode() != null;
}
private static boolean isSingleNodeSelected(SelectionAnalyzer sa, ISourceRange range, IDocument document,
ASTNode scope) throws BadLocationException {
return sa.getSelectedNodes().length == 1
&& !rangeIncludesNonWhitespaceOutsideNode(range, sa.getFirstSelectedNode(), document, scope);
}
private static boolean rangeIncludesNonWhitespaceOutsideNode(ISourceRange range, ASTNode node, IDocument document,
ASTNode scope) throws BadLocationException {
return Util.rangeIncludesNonWhitespaceOutsideRange(range, new SourceRange(node.getStart(), node.getLength()),
document);
}
/**
* Creates and returns a fragment representing the entire subtree rooted at
* <code>node</code>. It is not true in general that the node to which the
* produced IASTFragment maps (see {@link IASTFragment IASTFragment}) will
* be <code>node</code>.
*
* XXX: more doc (current assertions about input vs. output)
*
* @param astFragment
*/
public static IASTFragment createFragmentForFullSubtree(ASTNode node, IASTFragment astFragment) {
IASTFragment result = FragmentForFullSubtreeFactory.createFragmentFor(node, astFragment);
Assert.isNotNull(result);
return result;
}
private static class FragmentForSubPartBySourceRangeFactory extends FragmentFactory {
private ISourceRange fRange;
private ASTNode fScope;
private Exception modelException = null;
private IDocument fDocument;
public static IASTFragment createFragmentFor(ASTNode node, ISourceRange range, ASTNode scope,
IDocument document) throws Exception {
return new FragmentForSubPartBySourceRangeFactory().createFragment(node, range, scope, document);
}
@Override
public boolean visit(InfixExpression node) {
try {
setFragment(createInfixExpressionSubPartFragmentBySourceRange(node, fRange, fScope, fDocument));
} catch (Exception e) {
modelException = e;
}
return false;
}
@Override
public boolean visit(ASTNode node) {
// let fragment be null
return false;
}
protected IASTFragment createFragment(ASTNode node, ISourceRange range, ASTNode scope, IDocument document)
throws Exception {
fRange = range;
fScope = scope;
fDocument = document;
IASTFragment result = createFragment(node, null);
if (modelException != null)
throw modelException;
return result;
}
private static IExpressionFragment createInfixExpressionSubPartFragmentBySourceRange(InfixExpression node,
ISourceRange range, ASTNode scope, IDocument document) throws Exception {
return AssociativeInfixExpressionFragment.createSubPartFragmentBySourceRange(node, range, document);
}
}
private static class FragmentForFullSubtreeFactory extends FragmentFactory {
public static IASTFragment createFragmentFor(ASTNode node, IASTFragment astFragment) {
return new FragmentForFullSubtreeFactory().createFragment(node, astFragment);
}
@Override
public boolean visit(InfixExpression node) {
/*
* Try creating an associative infix expression fragment /* for the
* full subtree. If this is not applicable, try something more
* generic.
*/
IASTFragment fragment = AssociativeInfixExpressionFragment.createFragmentForFullSubtree(node);
if (fragment == null)
return visit((Expression) node);
if (oldFragment instanceof AssociativeInfixExpressionFragment
&& fragment instanceof AssociativeInfixExpressionFragment) {
AssociativeInfixExpressionFragment f1 = (AssociativeInfixExpressionFragment) oldFragment;
AssociativeInfixExpressionFragment f2 = (AssociativeInfixExpressionFragment) fragment;
if (f1.getOperands().size() != f2.getOperands().size()) {
return visit((Expression) node);
}
}
setFragment(fragment);
return false;
}
@Override
public boolean visit(Expression node) {
setFragment(new SimpleExpressionFragment(node));
return false;
}
@Override
public boolean visit(ASTNode node) {
setFragment(new SimpleFragment(node));
return false;
}
}
private static abstract class FragmentFactory extends HierarchicalVisitor {
private IASTFragment fFragment;
protected IASTFragment oldFragment;
protected IASTFragment createFragment(ASTNode node, IASTFragment astFragment) {
oldFragment = astFragment;
fFragment = null;
node.accept(this);
return fFragment;
}
protected final IASTFragment getFragment() {
return fFragment;
}
protected final void setFragment(IASTFragment fragment) {
Assert.isTrue(!isFragmentSet());
fFragment = fragment;
}
protected final boolean isFragmentSet() {
return getFragment() != null;
}
}
public static IASTFragment createFragmentForFullSubtree(ASTNode node) {
return createFragmentForFullSubtree(node, null);
}
}