View Javadoc

1   /*
2    * Copyright 2004-2009 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springmodules.validation.valang.javascript;
18  
19  import java.lang.reflect.Method;
20  import java.lang.reflect.Modifier;
21  import java.util.LinkedList;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  import org.springframework.util.Assert;
27  import org.springframework.util.CachingMapDecorator;
28  import org.springframework.util.ReflectionUtils;
29  
30  /**
31   * Helper implementation for a reflective visitor.
32   * Mainly for internal use within the framework.
33   *
34   * <p>To use, call <code>invokeVisit</code>, passing a Visitor object
35   * and the data argument to accept (double-dispatch). For example:
36   * 
37   * <pre>
38   *   public String styleValue(Object value) {
39   *     reflectiveVistorSupport.invokeVisit(this, value)
40   *   }
41   *  
42   *   // visit call back will be invoked via reflection
43   *   String visit(&lt;valueType&gt; arg) {
44   *     // process argument of type &lt;valueType&gt;
45   *   }
46   * </pre>
47   *
48   * See the {@link org.springframework.core.style.DefaultValueStyler} class
49   * for a concrete usage of this visitor helper.
50   * 
51   * <p><strong>Note</strong>: Copied from Spring 2.5 to avoid redoing work using this.
52   *
53   * @author Keith Donald
54   * @author Juergen Hoeller
55   * @since 1.2.2
56   * @deprecated as of Spring 2.5, to be removed in Spring 3.0
57   */
58  public class ReflectiveVisitorHelper {
59  
60      private static final String VISIT_METHOD = "visit";
61  
62      private static final String VISIT_NULL = "visitNull";
63  
64      private static final Log logger = LogFactory.getLog(ReflectiveVisitorHelper.class);
65  
66  
67      private final CachingMapDecorator visitorClassVisitMethods = new CachingMapDecorator() {
68          public Object create(Object key) {
69              return new ClassVisitMethods((Class) key);
70          }
71      };
72  
73  
74      /**
75       * Use reflection to call the appropriate <code>visit</code> method
76       * on the provided visitor, passing in the specified argument.
77       * @param visitor the visitor encapsulating the logic to process the argument
78       * @param argument the argument to dispatch
79       * @throws IllegalArgumentException if the visitor parameter is null
80       */
81      public Object invokeVisit(Object visitor, Object argument) {
82          Assert.notNull(visitor, "The visitor to visit is required");
83          // Perform call back on the visitor through reflection.
84          Method method = getMethod(visitor.getClass(), argument);
85          if (method == null) {
86              if (logger.isWarnEnabled()) {
87                  logger.warn("No method found by reflection for visitor class [" + visitor.getClass().getName()
88                          + "] and argument of type [" + (argument != null ? argument.getClass().getName() : "") + "]");
89              }
90              return null;
91          }
92          try {
93              Object[] args = null;
94              if (argument != null) {
95                  args = new Object[] {argument};
96              }
97              if (!Modifier.isPublic(method.getModifiers())) {
98                  method.setAccessible(true);
99              }
100             return method.invoke(visitor, args);
101         }
102         catch (Exception ex) {
103             ReflectionUtils.handleReflectionException(ex);
104             throw new IllegalStateException("Should never get here");
105         }
106     }
107 
108     /**
109      * Determines the most appropriate visit method for the
110      * given visitor class and argument.
111      */
112     private Method getMethod(Class visitorClass, Object argument) {
113         ClassVisitMethods visitMethods = (ClassVisitMethods) this.visitorClassVisitMethods.get(visitorClass);
114         return visitMethods.getVisitMethod(argument != null ? argument.getClass() : null);
115     }
116 
117 
118     /**
119      * Internal class caching visitor methods by argument class.
120      */
121     private static class ClassVisitMethods {
122 
123         private final Class visitorClass;
124 
125         private final CachingMapDecorator visitMethodCache = new CachingMapDecorator() {
126             public Object create(Object argumentClazz) {
127                 if (argumentClazz == null) {
128                     return findNullVisitorMethod();
129                 }
130                 Method method = findVisitMethod((Class) argumentClazz);
131                 if (method == null) {
132                     method = findDefaultVisitMethod();
133                 }
134                 return method;
135             }
136         };
137 
138         public ClassVisitMethods(Class visitorClass) {
139             this.visitorClass = visitorClass;
140         }
141 
142         private Method findNullVisitorMethod() {
143             for (Class clazz = this.visitorClass; clazz != null; clazz = clazz.getSuperclass()) {
144                 try {
145                     return clazz.getDeclaredMethod(VISIT_NULL, (Class[]) null);
146                 }
147                 catch (NoSuchMethodException ex) {
148                 }
149             }
150             return findDefaultVisitMethod();
151         }
152 
153         private Method findDefaultVisitMethod() {
154             final Class[] args = {Object.class};
155             for (Class clazz = this.visitorClass; clazz != null; clazz = clazz.getSuperclass()) {
156                 try {
157                     return clazz.getDeclaredMethod(VISIT_METHOD, args);
158                 }
159                 catch (NoSuchMethodException ex) {
160                 }
161             }
162             if (logger.isWarnEnabled()) {
163                 logger.warn("No default '" + VISIT_METHOD + "' method found. Returning <null>.");
164             }
165             return null;
166         }
167 
168         /**
169          * Gets a cached visitor method for the specified argument type.
170          */
171         private Method getVisitMethod(Class argumentClass) {
172             return (Method) this.visitMethodCache.get(argumentClass);
173         }
174 
175         /**
176          * Traverses class hierarchy looking for applicable visit() method.
177          */
178         private Method findVisitMethod(Class rootArgumentType) {
179             if (rootArgumentType == Object.class) {
180                 return null;
181             }
182             LinkedList classQueue = new LinkedList();
183             classQueue.addFirst(rootArgumentType);
184 
185             while (!classQueue.isEmpty()) {
186                 Class argumentType = (Class) classQueue.removeLast();
187                 // Check for a visit method on the visitor class matching this
188                 // argument type.
189                 try {
190                     if (logger.isTraceEnabled()) {
191                         logger.trace("Looking for method " + VISIT_METHOD + "(" + argumentType + ")");
192                     }
193                     return findVisitMethod(this.visitorClass, argumentType);
194                 }
195                 catch (NoSuchMethodException e) {
196                     // Queue up the argument super class if it's not of type Object.
197                     if (!argumentType.isInterface() && (argumentType.getSuperclass() != Object.class)) {
198                         classQueue.addFirst(argumentType.getSuperclass());
199                     }
200                     // Queue up argument's implemented interfaces.
201                     Class[] interfaces = argumentType.getInterfaces();
202                     for (int i = 0; i < interfaces.length; i++) {
203                         classQueue.addFirst(interfaces[i]);
204                     }
205                 }
206             }
207             // No specific method found -> return the default.
208             return findDefaultVisitMethod();
209         }
210 
211         private Method findVisitMethod(Class visitorClass, Class argumentType) throws NoSuchMethodException {
212             try {
213                 return visitorClass.getDeclaredMethod(VISIT_METHOD, new Class[] {argumentType});
214             }
215             catch (NoSuchMethodException ex) {
216                 // Try visitorClass superclasses.
217                 if (visitorClass.getSuperclass() != Object.class) {
218                     return findVisitMethod(visitorClass.getSuperclass(), argumentType);
219                 }
220                 else {
221                     throw ex;
222                 }
223             }
224         }
225     }
226 
227 }