View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.ast.xpath;
5   
6   import java.lang.reflect.Method;
7   import java.util.ArrayList;
8   import java.util.Collections;
9   import java.util.HashMap;
10  import java.util.Iterator;
11  import java.util.List;
12  import java.util.Map;
13  
14  import net.sourceforge.pmd.lang.ast.Node;
15  
16  public class AttributeAxisIterator implements Iterator<Attribute> {
17  
18      private static class MethodWrapper {
19          public Method method;
20          public String name;
21  
22          public MethodWrapper(Method m) {
23              this.method = m;
24              this.name = truncateMethodName(m.getName());
25          }
26  
27          private String truncateMethodName(String n) {
28              // about 70% of the methods start with 'get', so this case goes
29              // first
30              if (n.startsWith("get")) {
31                  return n.substring("get".length());
32              }
33              if (n.startsWith("is")) {
34                  return n.substring("is".length());
35              }
36              if (n.startsWith("has")) {
37                  return n.substring("has".length());
38              }
39              if (n.startsWith("uses")) {
40                  return n.substring("uses".length());
41              }
42  
43              return n;
44          }
45      }
46  
47      private Attribute currObj;
48      private MethodWrapper[] methodWrappers;
49      private int position;
50      private Node node;
51  
52      private static Map<Class<?>, MethodWrapper[]> methodCache =
53              Collections.synchronizedMap(new HashMap<Class<?>, MethodWrapper[]>());
54  
55      public AttributeAxisIterator(Node contextNode) {
56          this.node = contextNode;
57          if (!methodCache.containsKey(contextNode.getClass())) {
58              Method[] preFilter = contextNode.getClass().getMethods();
59              List<MethodWrapper> postFilter = new ArrayList<>();
60              for (Method element : preFilter) {
61                  if (isAttributeAccessor(element)) {
62                      postFilter.add(new MethodWrapper(element));
63                  }
64              }
65              methodCache.put(contextNode.getClass(), postFilter.toArray(new MethodWrapper[postFilter.size()]));
66          }
67          this.methodWrappers = methodCache.get(contextNode.getClass());
68  
69          this.position = 0;
70          this.currObj = getNextAttribute();
71      }
72  
73      public Attribute next() {
74          if (currObj == null) {
75              throw new IndexOutOfBoundsException();
76          }
77          Attribute ret = currObj;
78          currObj = getNextAttribute();
79          return ret;
80      }
81  
82      public boolean hasNext() {
83          return currObj != null;
84      }
85  
86      public void remove() {
87          throw new UnsupportedOperationException();
88      }
89  
90      private Attribute getNextAttribute() {
91          if (methodWrappers == null || position == methodWrappers.length) {
92              return null;
93          }
94          MethodWrapper m = methodWrappers[position++];
95          return new Attribute(node, m.name, m.method);
96      }
97  
98      protected boolean isAttributeAccessor(Method method) {
99  
100         String methodName = method.getName();
101         boolean deprecated = method.getAnnotation(Deprecated.class) != null;
102 
103         return !deprecated
104                 && (Integer.TYPE == method.getReturnType() || Boolean.TYPE == method.getReturnType()
105                 || Double.TYPE == method.getReturnType() || String.class == method.getReturnType())
106                 && method.getParameterTypes().length == 0
107                 && Void.TYPE != method.getReturnType()
108                 && !methodName.startsWith("jjt")
109                 && !methodName.equals("toString")
110                 && !methodName.equals("getScope")
111                 && !methodName.equals("getClass")
112                 && !methodName.equals("getTypeNameNode")
113                 && !methodName.equals("getImportedNameNode") && !methodName.equals("hashCode");
114     }
115 }