View Javadoc

1   package net.sf.sapjcosupport;
2   
3   import org.apache.commons.lang.NotImplementedException;
4   import org.apache.oro.text.regex.MalformedPatternException;
5   import org.apache.oro.text.regex.Pattern;
6   import org.apache.oro.text.regex.Perl5Compiler;
7   import org.apache.oro.text.regex.Perl5Matcher;
8   import org.apache.log4j.Logger;
9   import org.springframework.core.NestedRuntimeException;
10  
11  import java.lang.reflect.Field;
12  import java.util.ArrayList;
13  import java.util.Iterator;
14  import java.util.NoSuchElementException;
15  
16  /**
17   * Iterator implementation that iterates through the fields declared in a class,
18   * and retrieves the values for an instance.
19   * <p/>
20   * This implementation does not support item removal, and will throw a
21   * <code>NotImplementedException</code> when the {@link #remove()} method is called.
22   *
23   * @author Jo Vandermeeren
24   * @since 4 Jun, 2006 - 10:12:07 PM
25   */
26  public class ReflectionFieldIterator implements Iterator {
27      private Object instance;
28      private int counter;
29      private Field[] fields;
30      private Logger logger;
31  
32      /**
33       * Simple constructor.
34       *
35       * @param instance instance to operate on
36       * @see #ReflectionFieldIterator(Object, String)
37       */
38      public ReflectionFieldIterator(Object instance) {
39          this(instance, null);
40      }
41  
42      /**
43       * Advanced constructor.
44       * <p/>
45       * This constructor accepts a regular expression by which the field names are evaluated.<br/>
46       * Only fields with matching names will be considered; if the <code>regularExpression</code>
47       * parameter is null or does not compile to a pattern, all fields will be considered.
48       * <p/>
49       * If the instance passed to this constructor is null, no exception will be thrown, but the
50       * {@link #hasNext()} method will return <code>false</code>.
51       *
52       * @param instance          instance to operate on
53       * @param regularExpression regular expression pattern to match field names
54       */
55      public ReflectionFieldIterator(Object instance, String regularExpression) {
56          logger = Logger.getLogger(getClass());
57          this.instance = instance;
58          if (instance != null) {
59              fields = instance.getClass().getFields();
60              if (regularExpression != null) {
61                  filterFieldNames(regularExpression);
62              }
63          } else {
64              if (logger.isDebugEnabled()) {
65                  logger.debug("The instance that was passed to the iterator is null.");
66              }
67              fields = null;
68          }
69      }
70  
71      private void filterFieldNames(String regularExpression) {
72          if (fields == null) {
73              return;
74          }
75          Pattern pattern;
76          try {
77              pattern = new Perl5Compiler().compile(regularExpression);
78          } catch (MalformedPatternException e) {
79              if (logger.isInfoEnabled()) {
80                  logger.info("Regular expression did not compile to a pattern; all fields will be considered.", e);
81              }
82              return;
83          }
84          Perl5Matcher matcher = new Perl5Matcher();
85          ArrayList filteredFieldList = new ArrayList(fields.length);
86          for (int i = 0; i < fields.length; i++) {
87              if (matcher.matches(fields[i].getName(), pattern)) {
88                  filteredFieldList.add(fields[i]);
89              }
90          }
91          fields = (Field[]) filteredFieldList.toArray(new Field[filteredFieldList.size()]);
92      }
93  
94      /**
95       * Checks if more fields are available to iterate through.
96       *
97       * @return true if more fields are available
98       */
99      public boolean hasNext() {
100         if (fields == null) {
101             return false;
102         }
103         return counter < fields.length;
104     }
105 
106     /**
107      * Retrieves the next available field value.
108      *
109      * @return next object in sequence
110      * @throws NoSuchElementException if no more objects are available
111      */
112     public Object next() {
113         if (!hasNext()) {
114             throw new NoSuchElementException("No more elements available in iterator");
115         }
116         try {
117             fields[++counter].setAccessible(true);
118             return fields[counter].get(instance);
119         } catch (IllegalAccessException e) {
120             throw new IllegalAccessRuntimeException(e);
121         }
122     }
123 
124     /**
125      * @deprecated Do not use this method, it is not implemented and throws a runtime exception
126      */
127     public void remove() {
128         throw new NotImplementedException("This iterator implementation does not support item removal");
129     }
130 
131     /**
132      * RuntimeException wrapper for IllegalAccessException.
133      */
134     public class IllegalAccessRuntimeException extends RuntimeWrapperException {
135         /**
136          * Constructor.
137          * <p/>
138          * This method simply calls its parent with an <code>IllegalAccessException</code>
139          * instance and an appropriate message.
140          *
141          * @param exception IllegalAccessException to wrap
142          */
143         public IllegalAccessRuntimeException(final IllegalAccessException exception) {
144             super("Could not access field through reflection", exception);
145         }
146     }
147 
148     /**
149      * Wraps any Throwable instance in a RuntimeException.
150      */
151     public class RuntimeWrapperException extends NestedRuntimeException {
152 
153         /**
154          * Uses the message provided by the original throwable.
155          *
156          * @param throwable instance to wrap
157          */
158         public RuntimeWrapperException(Throwable throwable) {
159             super(throwable.getMessage(), throwable);
160         }
161 
162         /**
163          * Uses a specified messaged to pass to the parent constructor.
164          *
165          * @param message   message to associate
166          * @param throwable instance to wrap
167          */
168         public RuntimeWrapperException(String message, Throwable throwable) {
169             super(message, throwable);
170         }
171     }
172 }