All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.pmd.lang.java.ast.ASTAmbiguousName Maven / Gradle / Ivy

The newest version!
/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.lang.java.ast;

import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * An ambiguous name occurring in any context. Without a disambiguation
 * pass that taking care of obscuring rules and the current declarations
 * in scope, this node could be a type, package, or variable name -we
 * can't know for sure. The node is a placeholder for that unknown entity.
 * It implements both {@link ASTType} and {@link ASTPrimaryExpression} to
 * be able to be inserted in their hierarchy (maybe that should be changed
 * though).
 *
 * 

This node corresponds simultaneously to the AmbiguousName * and PackageOrTypeName productions of the JLS. * *

 * AmbiguousNameExpr ::= <IDENTIFIER> ( "." <IDENTIFIER>)*
 * 
* * @implNote *

Disambiguation

* *

Some ambiguous names are pushed by the expression parser because * we don't want to look too far ahead (in primary prefix). But it can * happen that the next segment (primary suffix) constrains the name to * be e.g. a type name or an expression name. E.g. From the JLS: * *

* A name is syntactically classified as an ExpressionName in these contexts: * ... * - As the qualifying expression in a qualified class instance creation * expression (§15.9) *
* * We don't know at the moment the name is parsed that it will be * followed by "." "new" and a constructor call. But as soon as the * {@link ASTConstructorCall} is pushed, we know that the LHS must be an * expression. In that case, the name can be reclassified, and e.g. if * it's a simple name be promoted to {@link ASTVariableAccess}. This * type of immediate disambiguation is carried out by the {@link AbstractJavaNode#jjtClose()} * method of those nodes that do force a specific context on their * left-hand side. See also {@link LeftRecursiveNode}. * *

Another mechanism is {@link #forceExprContext()} and {@link #forceTypeContext()}, * which are called by the parser to promote an ambiguous name to an * expression or a type when exiting from the {@link JavaParserImpl#PrimaryExpression()} * production or {@link JavaParserImpl#ClassOrInterfaceType()}. * *

Those two mechanisms perform the first classification step, the * one that only depends on the syntactic context and not on semantic * information. A second pass on the AST after building the symbol tables * would allow us to remove all the remaining ambiguous names. */ public final class ASTAmbiguousName extends AbstractJavaExpr implements ASTReferenceType, ASTPrimaryExpression { // if true, then this was explitly left in the tree by the disambig // pass, with a warning private boolean wasProcessed = false; ASTAmbiguousName(int id) { super(id); } ASTAmbiguousName(String id) { super(JavaParserImplTreeConstants.JJTAMBIGUOUSNAME); setImage(id); } /** Returns the entire name, including periods if any. */ public String getName() { return super.getImage(); } boolean wasProcessed() { return wasProcessed; } void setProcessed() { this.wasProcessed = true; } @Override protected R acceptVisitor(JavaVisitor visitor, P data) { return visitor.visit(this, data); } // Package-private construction methods: /** * Called by the parser if this ambiguous name was a full expression. * Then, since the node was in an expression syntactic context, we * can do some preliminary reclassification: *

    *
  • If the name is a single identifier, then this can be * reclassified as an {@link ASTVariableAccess} *
  • If the name is a sequence of identifiers, then the last * segment can be reclassified as an {@link ASTFieldAccess}, * and the rest of the sequence (to the left) is left ambiguous. *
* * @return the node which will replace this node in the tree */ ASTExpression forceExprContext() { // by the time this is called, this node is on top of the stack, // meaning, it has no parent return shrinkOneSegment(ASTVariableAccess::new, ASTFieldAccess::new); } /** * Called by the parser if this ambiguous name was expected to be * a type name. Then we simply promote it to an {@link ASTClassType} * with the appropriate LHS. * * @return the node which will replace this node in the tree */ ASTClassType forceTypeContext() { // same, there's no parent here return shrinkOneSegment(ASTClassType::new, ASTClassType::new); } /** * Low level method to reclassify this ambiguous name. Basically * the name is split in two: the part before the last dot, and the * part after it. * * @param simpleNameHandler Called with this name as parameter if * this ambiguous name is a simple name. * No resizing of the node is performed. * * @param splitNameConsumer Called with this node as first parameter, * and the last name segment as second * parameter. After the handler is executed, * the text bounds of this node are shrunk * to fit to only the left part. The handler * may e.g. move the node to another parent. * @param Result type * * @return The node that will replace this one. */ private T shrinkOneSegment(Function simpleNameHandler, BiFunction splitNameConsumer) { String image = getName(); int lastDotIdx = image.lastIndexOf('.'); if (lastDotIdx < 0) { T res = simpleNameHandler.apply(this); if (res != null) { res.copyTextCoordinates(this); } return res; } String lastSegment = image.substring(lastDotIdx + 1); String remainingAmbiguous = image.substring(0, lastDotIdx); T res = splitNameConsumer.apply(this, lastSegment); // copy coordinates before shrinking if (res != null) { res.copyTextCoordinates(this); } // shift the ident + the dot this.shiftTokens(0, -2); setImage(remainingAmbiguous); return res; } /** * Delete this name from the children of the parent. */ void deleteInParent() { AbstractJavaNode parent = (AbstractJavaNode) getParent(); parent.removeChildAtIndex(this.getIndexInParent()); } /** * A specialized version of {@link #shrinkOneSegment(Function, BiFunction)} * for nodes that carry the unambiguous part as their own image. * Basically the last segment is set as the image of the parent * node, and no node corresponds to it. */ void shrinkOrDeleteInParentSetImage() { // the params of the lambdas here are this object, // but if we use them instead of this, we avoid capturing the // this reference and the lambdas can be optimised to a singleton shrinkOneSegment( simpleName -> { String simpleNameImage = simpleName.getFirstToken().getImage(); AbstractJavaNode parent = (AbstractJavaNode) simpleName.getParent(); if (parent instanceof ASTClassType) { ((ASTClassType) parent).setSimpleName(simpleNameImage); } else { parent.setImage(simpleName.getFirstToken().getImage()); } parent.removeChildAtIndex(simpleName.getIndexInParent()); return null; }, (ambig, simpleName) -> { AbstractJavaNode parent = (AbstractJavaNode) ambig.getParent(); if (parent instanceof ASTClassType) { ((ASTClassType) parent).setSimpleName(simpleName); } else { parent.setImage(simpleName); } return null; } ); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy