diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/designerbindings/DesignerBindings.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/designerbindings/DesignerBindings.java index 46a0ee4254..a01f6f178c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/designerbindings/DesignerBindings.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/designerbindings/DesignerBindings.java @@ -4,7 +4,12 @@ package net.sourceforge.pmd.util.designerbindings; +import java.util.Collection; +import java.util.Collections; + import net.sourceforge.pmd.annotation.Experimental; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.Attribute; import net.sourceforge.pmd.lang.symboltable.ScopedNode; /** @@ -25,6 +30,106 @@ public interface DesignerBindings { RelatedNodesSelector getRelatedNodesSelector(); + /** + * Returns a collection of "additional information" entries pertaining to + * the given node. An entry may look like {@code ("Type = List", 0)}, + * or show the result of an XPath function. The information is shown + * when the node is displayed. + * + *

Order of the collection is unimportant, it's sorted using + * {@link AdditionalInfo#getSortKey()}. + */ + Collection getAdditionalInfo(Node node); + + + /** + * An entry for the "additional info" panel. + */ + class AdditionalInfo { + + private final String sortKey; + private final String display; + + + public AdditionalInfo(String sortKey, String display) { + this.sortKey = sortKey; + this.display = display; + } + + public AdditionalInfo(String display) { + this(display, display); + } + + /** + * Returns the string used to sort the additional info. + * For example, returning {@code "A"} ensures this is displayed + * first, provided there's no other entry with an {@code "A"}. + */ + public String getSortKey() { + return sortKey; + } + + /** + * Returns the string displayed to the user. + */ + public String getDisplayString() { + return display; + } + } + + + /** + * Returns the "main" attribute of the given node. + * The string representation of this attribute ({@link Attribute#getStringValue()}) + * will be displayed next to the node type in the treeview. For + * example, for a numeric literal, this could return the attribute + * {@code (@IntValue, 1)}, for a class declaration, it could return the name + * of the class (eg {@code (@SimpleName, String)}. + * + *

If there's no obvious "main" attribute, or if the node is not + * supported, returns null. If the returned attribute is non-null, + * but its string value is, the return value is ignored. + * + *

Note: the attribute doesn't need to originate from + * {@link Node#getXPathAttributesIterator()}, it can be constructed + * ad-hoc. The name of the attribute should be a valid name for the + * XPath attribute though. + * + *

This method is meant to break the designer's dependency on {@link Node#getImage()}. + */ + // @Nullable + Attribute getMainAttribute(Node node); + + + /** + * Returns true if the children of this node should be displayed in + * the treeview by default. Returning "true" is the safe default value. + */ + boolean isExpandedByDefaultInTree(Node node); + + + /** + * Returns a constant describing an icon that the node should bear + * in the treeview and other relevant places. Returns null if no icon + * is applicable. + */ + // @Nullable + TreeIconId getIcon(Node node); + + + /** + * See {@link #getIcon(Node)}. + */ + @Experimental + enum TreeIconId { + CLASS, + METHOD, + CONSTRUCTOR, + FIELD, + VARIABLE + } + + /** * A base implementation for {@link DesignerBindings}. */ @@ -37,6 +142,30 @@ public interface DesignerBindings { return null; } + @Override + public Collection getAdditionalInfo(Node node) { + return Collections.emptyList(); + } + + @Override + public Attribute getMainAttribute(Node node) { + String image = node.getImage(); + if (image != null) { + return new Attribute(node, "Image", image); + } + return null; + } + + @Override + public boolean isExpandedByDefaultInTree(Node node) { + return true; + } + + @Override + public TreeIconId getIcon(Node node) { + return null; + } + /** Returns the default instance. */ public static DefaultDesignerBindings getInstance() { return INSTANCE; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java index 58485f357a..afee0df9c6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java @@ -21,6 +21,7 @@ import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.java.dfa.DataFlowFacade; import net.sourceforge.pmd.lang.java.dfa.JavaDFAGraphRule; +import net.sourceforge.pmd.lang.java.internal.JavaDesignerBindings; import net.sourceforge.pmd.lang.java.multifile.MultifileVisitorFacade; import net.sourceforge.pmd.lang.java.qname.QualifiedNameResolver; import net.sourceforge.pmd.lang.java.rule.JavaRuleViolationFactory; @@ -34,6 +35,7 @@ import net.sourceforge.pmd.lang.java.xpath.TypeIsFunction; import net.sourceforge.pmd.lang.java.xpath.TypeOfFunction; import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; +import net.sourceforge.pmd.util.designerbindings.DesignerBindings; import net.sf.saxon.sxpath.IndependentContext; @@ -151,6 +153,10 @@ public abstract class AbstractJavaHandler extends AbstractLanguageVersionHandler }; } + @Override + public DesignerBindings getDesignerBindings() { + return JavaDesignerBindings.INSTANCE; + } @Override public DFAGraphRule getDFAGraphRule() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaDesignerBindings.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaDesignerBindings.java new file mode 100644 index 0000000000..ff1f44ba3a --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaDesignerBindings.java @@ -0,0 +1,92 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.internal; + +import java.util.Collection; +import java.util.Collections; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.Attribute; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTRecordConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.ast.JavaParserVisitor; +import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorReducedAdapter; +import net.sourceforge.pmd.lang.java.ast.TypeNode; +import net.sourceforge.pmd.util.designerbindings.DesignerBindings.DefaultDesignerBindings; + +public final class JavaDesignerBindings extends DefaultDesignerBindings { + + public static final JavaDesignerBindings INSTANCE = new JavaDesignerBindings(); + + private JavaDesignerBindings() { + + } + + @Override + public Attribute getMainAttribute(Node node) { + if (node instanceof JavaNode) { + Attribute attr = (Attribute) ((JavaNode) node).jjtAccept(MainAttrVisitor.INSTANCE, null); + if (attr != null) { + return attr; + } + } + + + return super.getMainAttribute(node); + } + + @Override + public TreeIconId getIcon(Node node) { + if (node instanceof ASTFieldDeclaration) { + return TreeIconId.FIELD; + } else if (node instanceof ASTAnyTypeDeclaration) { + return TreeIconId.CLASS; + } else if (node instanceof ASTMethodDeclaration) { + return TreeIconId.METHOD; + } else if (node instanceof ASTConstructorDeclaration + || node instanceof ASTRecordConstructorDeclaration) { + return TreeIconId.CONSTRUCTOR; + } else if (node instanceof ASTVariableDeclaratorId) { + return TreeIconId.VARIABLE; + } + return super.getIcon(node); + } + + @Override + public Collection getAdditionalInfo(Node node) { + if (node instanceof TypeNode) { + Class type = ((TypeNode) node).getType(); + if (type != null) { + return Collections.singletonList(new AdditionalInfo("Type: " + type)); + } + } + return super.getAdditionalInfo(node); + } + + private static final class MainAttrVisitor extends JavaParserVisitorReducedAdapter { + + private static final JavaParserVisitor INSTANCE = new MainAttrVisitor(); + + @Override + public Object visit(JavaNode node, Object data) { + return null; // don't recurse + } + + @Override + public Object visit(ASTAnyTypeDeclaration node, Object data) { + return new Attribute(node, "SimpleName", node.getSimpleName()); + } + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + return new Attribute(node, "Name", node.getName()); + } + } +}