package org.rubypeople.rdt.internal.ti.util; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.jruby.ast.ArrayNode; import org.jruby.ast.FCallNode; import org.jruby.ast.Node; import org.jruby.ast.StrNode; import org.jruby.ast.SymbolNode; import org.rubypeople.rdt.internal.core.parser.InOrderVisitor; /** * Visitor to find all instance and class attribute declarations (attr_*, cattr_*) within a specific scope. * * @author Jason Morrison */ public class AttributeLocator extends InOrderVisitor { /** Running total of results; is a Set to ensure uniqueness */ private Set<String> attributes; /** * Finds all instance attributes within a given node by looking for attr_* calls * * @param rootNode * @return */ public Collection<String> findInstanceAttributesInScope(Node rootNode) { if (rootNode == null) return Collections.emptyList(); attributes = new HashSet<String>(); rootNode.accept(this); Collection<String> result = Collections.unmodifiableSet(attributes); attributes = null; return result; } /** * Searches via InOrderVisitor for matches */ @Override public Object visitFCallNode(FCallNode fCallNode) { // Set up the prefix for instance (@) or class (@@) attributes String attrPrefix = null; if (isInstanceAttributeDeclaration(fCallNode.getName())) { attrPrefix = "@"; } else if (isClassAttributeDeclaration(fCallNode.getName())) { attrPrefix = "@@"; } if (attrPrefix == null) return super.visitFCallNode(fCallNode); // Look for an array of symbols or strings - these are the instance variables being declared Node argsNode = fCallNode.getArgsNode(); if (!(argsNode instanceof ArrayNode)) return super.visitFCallNode(fCallNode); ArrayNode arrayNode = (ArrayNode) argsNode; for (Node argNode : arrayNode.childNodes()) { // The nodes are found - record them! if (argNode instanceof SymbolNode) { attributes.add(attrPrefix + ((SymbolNode) argNode).getName()); } else if (argNode instanceof StrNode) { attributes.add(attrPrefix + ((StrNode) argNode).getValue()); } } return super.visitFCallNode(fCallNode); } /** * Returns whether the specified method name is an instance attribute declaration (i.e. attr_* :foo, 'bar', "baz") * * @param methodName * Method name to test * @return */ private boolean isInstanceAttributeDeclaration(String methodName) { return (methodName.equals("attr") || methodName.equals("attr_reader") || methodName.equals("attr_writer") || methodName .equals("attr_accessor")); } /** * Returns whether the specified method name is a class attribute declaration (i.e. cattr_* :foo, 'bar', "baz") * Non-standard, but conventional enough to be helpful, I believe. * * @param methodName * Method name to test * @return */ private boolean isClassAttributeDeclaration(String methodName) { return (methodName.equals("cattr") || methodName.equals("cattr_reader") || methodName.equals("cattr_writer") || methodName .equals("cattr_accessor")); } }