001: /*
002: * Copyright 2005 Joe Walker
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.directwebremoting.util;
017:
018: import java.io.IOException;
019: import java.io.UnsupportedEncodingException;
020: import java.io.Closeable;
021: import java.lang.reflect.Field;
022: import java.lang.reflect.InvocationTargetException;
023: import java.lang.reflect.Method;
024: import java.net.URLDecoder;
025: import java.util.ArrayList;
026: import java.util.Arrays;
027: import java.util.HashSet;
028: import java.util.List;
029: import java.util.Locale;
030: import java.util.Map;
031: import java.util.Set;
032:
033: import javax.servlet.ServletConfig;
034: import javax.servlet.ServletContext;
035: import javax.servlet.http.HttpServletRequest;
036: import javax.servlet.http.HttpServletResponse;
037: import javax.servlet.http.HttpSession;
038: import javax.xml.transform.TransformerFactoryConfigurationError;
039:
040: import org.apache.commons.logging.LogFactory;
041: import org.apache.commons.logging.Log;
042:
043: /**
044: * Various utilities, stuff that we're still surprised isn't in the JDK, and
045: * stuff that perhaps is borderline JDK material, but isn't really pure DWR
046: * either.
047: * @author Joe Walker [joe at getahead dot ltd dot uk]
048: */
049: public final class LocalUtil {
050: /**
051: * splitInbound() returns the type info in this parameter
052: */
053: public static final int INBOUND_INDEX_TYPE = 0;
054:
055: /**
056: * splitInbound() returns the value info in this parameter
057: */
058: public static final int INBOUND_INDEX_VALUE = 1;
059:
060: /**
061: * Prevent instantiation
062: */
063: private LocalUtil() {
064: }
065:
066: /**
067: * Create a string by joining the array elements together with the separator
068: * in-between each element. A null (in the array or as a separator) is
069: * treated as an empty string.
070: * @param array The array of elements to join
071: * @param separator The string sequence to place between array elements
072: * @return A string containing the joined elements
073: */
074: public static String join(Object[] array, String separator) {
075: if (array == null) {
076: return null;
077: }
078:
079: if (separator == null) {
080: separator = "";
081: }
082:
083: StringBuffer buffer = new StringBuffer();
084: boolean isFirst = true;
085:
086: for (Object object : array) {
087: if (isFirst) {
088: isFirst = false;
089: } else {
090: buffer.append(separator);
091: }
092:
093: if (object != null) {
094: buffer.append(object);
095: }
096: }
097:
098: return buffer.toString();
099: }
100:
101: /**
102: * Determines if the specified string is permissible as a Java identifier.
103: * Returns true if the string is non-null, non-zero length with a Java
104: * identifier start as the first character and Java identifier parts in all
105: * remaining characters.
106: * @param test the string to be tested.
107: * @return true if the string is a Java identifier, false otherwise.
108: * @see java.lang.Character#isJavaIdentifierPart(char)
109: * @see java.lang.Character#isJavaIdentifierStart(char)
110: */
111: public static boolean isJavaIdentifier(String test) {
112: if (test == null || test.length() == 0) {
113: return false;
114: }
115:
116: if (!Character.isJavaIdentifierStart(test.charAt(0))
117: && test.charAt(0) != '_') {
118: return false;
119: }
120:
121: for (int i = 1; i < test.length(); i++) {
122: if (!Character.isJavaIdentifierPart(test.charAt(i))
123: && test.charAt(i) != '_') {
124: return false;
125: }
126: }
127:
128: return true;
129: }
130:
131: /**
132: * Determines if the specified string contains only Unicode letters or
133: * digits as defined by {@link Character#isLetterOrDigit(char)}
134: * @param test The string to test
135: * @return true if the string is non-null, non-empty and contains only
136: * characters that are unicode letters or digits
137: * @see Character#isLetterOrDigit(char)
138: */
139: public static boolean isLetterOrDigitOrUnderline(String test) {
140: if (test == null || test.length() == 0) {
141: return false;
142: }
143:
144: for (int i = 0; i < test.length(); i++) {
145: if (!Character.isLetterOrDigit(test.charAt(i))
146: && test.charAt(i) != '_') {
147: return false;
148: }
149: }
150:
151: return true;
152: }
153:
154: /**
155: * True if c1 is java.lang.Boolean and c2 is boolean, etc.
156: * @param c1 the first class to test
157: * @param c2 the second class to test
158: * @return true if the classes are equivalent
159: */
160: public static boolean isEquivalent(Class<?> c1, Class<?> c2) {
161: if (c1 == Boolean.class || c1 == Boolean.TYPE) {
162: return c2 == Boolean.class || c2 == Boolean.TYPE;
163: } else if (c1 == Byte.class || c1 == Byte.TYPE) {
164: return c2 == Byte.class || c2 == Byte.TYPE;
165: } else if (c1 == Character.class || c1 == Character.TYPE) {
166: return c2 == Character.class || c2 == Character.TYPE;
167: } else if (c1 == Short.class || c1 == Short.TYPE) {
168: return c2 == Short.class || c2 == Short.TYPE;
169: } else if (c1 == Integer.class || c1 == Integer.TYPE) {
170: return c2 == Integer.class || c2 == Integer.TYPE;
171: } else if (c1 == Long.class || c1 == Long.TYPE) {
172: return c2 == Long.class || c2 == Long.TYPE;
173: } else if (c1 == Float.class || c1 == Float.TYPE) {
174: return c2 == Float.class || c2 == Float.TYPE;
175: } else if (c1 == Double.class || c1 == Double.TYPE) {
176: return c2 == Double.class || c2 == Double.TYPE;
177: } else if (c1 == Void.class || c1 == Void.TYPE) {
178: return c2 == Void.class || c2 == Void.TYPE;
179: }
180:
181: return false;
182: }
183:
184: /**
185: * @param type The class to de-primitivize
186: * @return The non-primitive version of the class
187: */
188: public static Class<?> getNonPrimitiveType(Class<?> type) {
189: if (!type.isPrimitive()) {
190: return type;
191: } else if (type == Boolean.TYPE) {
192: return Boolean.class;
193: } else if (type == Byte.TYPE) {
194: return Byte.class;
195: } else if (type == Character.TYPE) {
196: return Character.class;
197: } else if (type == Short.TYPE) {
198: return Short.class;
199: } else if (type == Integer.TYPE) {
200: return Integer.class;
201: } else if (type == Long.TYPE) {
202: return Long.class;
203: } else if (type == Float.TYPE) {
204: return Float.class;
205: } else if (type == Double.TYPE) {
206: return Double.class;
207: } else if (type == Void.TYPE) {
208: return Void.class;
209: }
210:
211: return null;
212: }
213:
214: /**
215: * Add headers to prevent browsers and proxies from caching this reply.
216: * @param resp The response to add headers to
217: */
218: public static void addNoCacheHeaders(HttpServletResponse resp) {
219: // Set standard HTTP/1.1 no-cache headers.
220: resp.setHeader("Cache-Control",
221: "no-store, no-cache, must-revalidate");
222:
223: // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
224: resp.addHeader("Cache-Control", "post-check=0, pre-check=0");
225:
226: // Set standard HTTP/1.0 no-cache header.
227: resp.setHeader("Pragma", "no-cache");
228:
229: // Set to expire far in the past. Prevents caching at the proxy server
230: resp.setHeader("Expires", "Sat, 6 May 1995 12:00:00 GMT");
231: }
232:
233: /**
234: * Is this class one that we auto fill, so the user can ignore?
235: * @param paramType The type to test
236: * @return true if the type is a Servlet type
237: */
238: public static boolean isServletClass(Class<?> paramType) {
239: return paramType == HttpServletRequest.class
240: || paramType == HttpServletResponse.class
241: || paramType == ServletConfig.class
242: || paramType == ServletContext.class
243: || paramType == HttpSession.class;
244: }
245:
246: /**
247: * URL decode a value.
248: * {@link URLDecoder#decode(String, String)} throws an
249: * {@link UnsupportedEncodingException}, which is silly given that the most
250: * common use case will be to pass in "UTF-8"
251: * @param value The string to decode
252: * @return The decoded string
253: */
254: public static String decode(String value) {
255: try {
256: return URLDecoder.decode(value, "UTF-8");
257: } catch (UnsupportedEncodingException ex) {
258: log.error("UTF-8 is not a valid char sequence?", ex);
259: return value;
260: }
261: }
262:
263: /**
264: * Set use reflection to set the setters on the object called by the keys
265: * in the params map with the corresponding values
266: * @param object The object to setup
267: * @param params The settings to use
268: * @param ignore List of keys to not warn about if they are not properties
269: * Note only the warning is skipped, we still try the setter
270: */
271: public static void setParams(Object object, Map<String, ?> params,
272: List<String> ignore) {
273: for (Map.Entry<String, ?> entry : params.entrySet()) {
274: String key = entry.getKey();
275: Object value = entry.getValue();
276:
277: try {
278: setProperty(object, key, value);
279: } catch (NoSuchMethodException ex) {
280: if (ignore != null && !ignore.contains(key)) {
281: log.warn("No property '" + key + "' on "
282: + object.getClass().getName());
283: }
284: } catch (InvocationTargetException ex) {
285: log.warn("Error setting " + key + "=" + value + " on "
286: + object.getClass().getName(), ex
287: .getTargetException());
288: } catch (Exception ex) {
289: log.warn("Error setting " + key + "=" + value + " on "
290: + object.getClass().getName(), ex);
291: }
292: }
293: }
294:
295: /**
296: * Set a property on an object using reflection
297: * @param object The object to call the setter on
298: * @param key The name of the property to set.
299: * @param value The new value to use for the property
300: * @throws NoSuchMethodException Passed on from reflection code
301: * @throws SecurityException Passed on from reflection code
302: * @throws IllegalAccessException Passed on from reflection code
303: * @throws IllegalArgumentException Passed on from reflection code
304: * @throws InvocationTargetException Passed on from reflection code
305: */
306: public static void setProperty(Object object, String key,
307: Object value) throws NoSuchMethodException,
308: SecurityException, IllegalAccessException,
309: IllegalArgumentException, InvocationTargetException {
310: Class<? extends Object> real = object.getClass();
311:
312: String setterName = "set"
313: + key.substring(0, 1).toUpperCase(Locale.ENGLISH)
314: + key.substring(1);
315:
316: try {
317: // Can we work with whatever type we were given?
318: Method method = real
319: .getMethod(setterName, value.getClass());
320: method.invoke(object, value);
321: return;
322: } catch (NoSuchMethodException ex) {
323: // If it is a string then next we try to coerce it to the right type
324: // otherwise we give up.
325: if (!(value instanceof String)) {
326: throw ex;
327: }
328: }
329:
330: for (Method setter : real.getMethods()) {
331: if (setter.getName().equals(setterName)
332: && setter.getParameterTypes().length == 1) {
333: Class<?> propertyType = setter.getParameterTypes()[0];
334: try {
335: Object param = simpleConvert((String) value,
336: propertyType);
337: setter.invoke(object, param);
338: return;
339: } catch (IllegalArgumentException ex) {
340: // The conversion failed - it was speculative anyway so we
341: // don't worry now
342: }
343: }
344: }
345:
346: throw new NoSuchMethodException(
347: "Failed to find a property called: " + key + " on "
348: + object.getClass().getName());
349: }
350:
351: /**
352: * Can the type be used in a call to {@link #simpleConvert(String, Class)}?
353: * @param paramType The type to test
354: * @return true if the type is acceptable to simpleConvert()
355: */
356: public static boolean isTypeSimplyConvertable(Class<?> paramType) {
357: return paramType == String.class || paramType == Integer.class
358: || paramType == Integer.TYPE
359: || paramType == Short.class || paramType == Short.TYPE
360: || paramType == Byte.class || paramType == Byte.TYPE
361: || paramType == Long.class || paramType == Long.TYPE
362: || paramType == Float.class || paramType == Float.TYPE
363: || paramType == Double.class
364: || paramType == Double.TYPE
365: || paramType == Character.class
366: || paramType == Character.TYPE
367: || paramType == Boolean.class
368: || paramType == Boolean.TYPE;
369: }
370:
371: /**
372: * A very simple conversion function for all the IoC style setup and
373: * reflection that we are doing.
374: * @param value The value to convert
375: * @param paramType The type to convert to. Currently any primitive types and
376: * String are supported.
377: * @return The converted object.
378: */
379: @SuppressWarnings("unchecked")
380: public static <T> T simpleConvert(String value, Class<T> paramType) {
381: if (paramType == String.class) {
382: return (T) value;
383: }
384:
385: if (paramType == Character.class || paramType == Character.TYPE) {
386: value = LocalUtil.decode(value);
387: if (value.length() == 1) {
388: return (T) Character.valueOf(value.charAt(0));
389: } else {
390: throw new IllegalArgumentException(
391: "Can't more than one character in string - can't convert to char: '"
392: + value + "'");
393: }
394: }
395:
396: String trimValue = value.trim();
397:
398: if (paramType == Boolean.class) {
399: if (trimValue.length() == 0) {
400: return null;
401: }
402:
403: return (T) Boolean.valueOf(trimValue);
404: }
405:
406: if (paramType == Boolean.TYPE) {
407: return (T) Boolean.valueOf(trimValue);
408: }
409:
410: if (paramType == Integer.class) {
411: if (trimValue.length() == 0) {
412: return null;
413: }
414:
415: return (T) Integer.valueOf(trimValue);
416: }
417:
418: if (paramType == Integer.TYPE) {
419: if (trimValue.length() == 0) {
420: return (T) Integer.valueOf(0);
421: }
422:
423: return (T) Integer.valueOf(trimValue);
424: }
425:
426: if (paramType == Short.class) {
427: if (trimValue.length() == 0) {
428: return null;
429: }
430:
431: return (T) Short.valueOf(trimValue);
432: }
433:
434: if (paramType == Short.TYPE) {
435: if (trimValue.length() == 0) {
436: return (T) Short.valueOf((short) 0);
437: }
438:
439: return (T) Short.valueOf(trimValue);
440: }
441:
442: if (paramType == Byte.class) {
443: if (trimValue.length() == 0) {
444: return null;
445: }
446:
447: return (T) Byte.valueOf(trimValue);
448: }
449:
450: if (paramType == Byte.TYPE) {
451: if (trimValue.length() == 0) {
452: return (T) Byte.valueOf((byte) 0);
453: }
454:
455: return (T) Byte.valueOf(trimValue);
456: }
457:
458: if (paramType == Long.class) {
459: if (trimValue.length() == 0) {
460: return null;
461: }
462:
463: return (T) Long.valueOf(trimValue);
464: }
465:
466: if (paramType == Long.TYPE) {
467: if (trimValue.length() == 0) {
468: return (T) Long.valueOf(0);
469: }
470:
471: return (T) Long.valueOf(trimValue);
472: }
473:
474: if (paramType == Float.class) {
475: if (trimValue.length() == 0) {
476: return null;
477: }
478:
479: return (T) Float.valueOf(trimValue);
480: }
481:
482: if (paramType == Float.TYPE) {
483: if (trimValue.length() == 0) {
484: return (T) Float.valueOf(0);
485: }
486:
487: return (T) Float.valueOf(trimValue);
488: }
489:
490: if (paramType == Double.class) {
491: if (trimValue.length() == 0) {
492: return null;
493: }
494:
495: return (T) Double.valueOf(trimValue);
496: }
497:
498: if (paramType == Double.TYPE) {
499: if (trimValue.length() == 0) {
500: return (T) Double.valueOf(0.0D);
501: }
502:
503: return (T) Double.valueOf(trimValue);
504: }
505:
506: throw new IllegalArgumentException(
507: "Unsupported conversion type: " + paramType.getName());
508: }
509:
510: /**
511: * Is this object property one that we can use in a JSON style or do we need
512: * to get fancy. i.e does it contain only letters and numbers with an
513: * initial letter.
514: * @param name The name to test for JSON compatibility
515: * @return true if the name is simple
516: */
517: public static boolean isSimpleName(String name) {
518: if (name.length() == 0) {
519: return false;
520: }
521:
522: if (JavascriptUtil.isReservedWord(name)) {
523: return false;
524: }
525:
526: boolean isSimple = Character.isLetter(name.charAt(0));
527: for (int i = 1; isSimple && i < name.length(); i++) {
528: if (!Character.isLetterOrDigit(name.charAt(i))) {
529: isSimple = false;
530: }
531: }
532:
533: return isSimple;
534: }
535:
536: /**
537: * Utility to essentially do Class forName and allow configurable
538: * Classloaders.
539: * <p>The initial implementation makes use of the context classloader for
540: * the current thread.
541: * @param className The class to create
542: * @return The class if it is safe or null otherwise.
543: * @throws ClassNotFoundException If <code>className</code> is not valid
544: */
545: public static Class<?> classForName(String className)
546: throws ClassNotFoundException {
547: // Class.forName(className);
548: return Thread.currentThread().getContextClassLoader()
549: .loadClass(className);
550: }
551:
552: /**
553: * Calling methods using reflection is useful for graceful fallback - this
554: * is a helper method to make this easy
555: * @param object The object to use as 'this'
556: * @param method The method to call, can be null in which case null is returned
557: * @param params The parameters to pass to the reflection call
558: * @return The results of calling method.invoke() or null
559: * @throws IllegalStateException If anything goes wrong
560: */
561: public static Object invoke(Object object, Method method,
562: Object[] params) throws IllegalStateException {
563: Object reply = null;
564: if (method != null) {
565: try {
566: reply = method.invoke(object, params);
567: } catch (InvocationTargetException ex) {
568: throw new IllegalStateException(
569: "InvocationTargetException calling "
570: + method.getName() + ": "
571: + ex.getTargetException().toString());
572: } catch (Exception ex) {
573: throw new IllegalStateException(
574: "Reflection error calling " + method.getName()
575: + ": " + ex.toString());
576: }
577: }
578:
579: return reply;
580: }
581:
582: /**
583: * Utility to essentially do Class forName with the assumption that the
584: * environment expects failures for missing jar files and can carry on if
585: * this process fails.
586: * @param <T> The base type that we want a class to implement
587: * @param debugContext The name for debugging purposes
588: * @param className The class to create
589: * @param impl The implementation class - what should className do?
590: * @return The class if it is safe or null otherwise.
591: */
592: @SuppressWarnings("unchecked")
593: public static <T> Class<? extends T> classForName(
594: String debugContext, String className, Class<T> impl) {
595: Class<? extends T> clazz;
596:
597: try {
598: clazz = (Class<? extends T>) classForName(className);
599: } catch (ClassNotFoundException ex) {
600: // We expect this sometimes, hence debug
601: log.debug("Skipping '" + debugContext
602: + "' due to ClassNotFoundException on " + className
603: + ". Cause: " + ex.getMessage());
604: return null;
605: } catch (NoClassDefFoundError ex) {
606: // We expect this sometimes, hence debug
607: log.debug("Skipping '" + debugContext
608: + "' due to NoClassDefFoundError on " + className
609: + ". Cause: " + ex.getMessage());
610: return null;
611: } catch (TransformerFactoryConfigurationError ex) {
612: // We expect this sometimes, hence debug
613: log
614: .debug("Skipping '"
615: + debugContext
616: + "' due to TransformerFactoryConfigurationError on "
617: + className + ". Cause: " + ex.getMessage());
618: log
619: .debug("Maybe you need to add xalan.jar to your webserver?");
620: return null;
621: }
622:
623: // Check it is of the right type
624: if (!impl.isAssignableFrom(clazz)) {
625: log.error("Class '" + clazz.getName()
626: + "' does not implement '" + impl.getName() + "'.");
627: return null;
628: }
629:
630: // Check we can create it
631: try {
632: clazz.newInstance();
633: } catch (InstantiationException ex) {
634: log.error("InstantiationException for '" + debugContext
635: + "' failed:", ex);
636: return null;
637: } catch (IllegalAccessException ex) {
638: log.error("IllegalAccessException for '" + debugContext
639: + "' failed:", ex);
640: return null;
641: } catch (NoClassDefFoundError ex) {
642: // We expect this sometimes, hence debug
643: log.debug("Skipping '" + debugContext
644: + "' due to NoClassDefFoundError on " + className
645: + ". Cause: " + ex.getMessage());
646: return null;
647: } catch (TransformerFactoryConfigurationError ex) {
648: // We expect this sometimes, hence debug
649: log
650: .debug("Skipping '"
651: + debugContext
652: + "' due to TransformerFactoryConfigurationError on "
653: + className + ". Cause: " + ex.getMessage());
654: log
655: .debug("Maybe you need to add xalan.jar to your webserver?");
656: return null;
657: } catch (Exception ex) {
658: // For some reason we can't catch this?
659: if (ex instanceof ClassNotFoundException) {
660: // We expect this sometimes, hence debug
661: log.debug("Skipping '" + debugContext
662: + "' due to ClassNotFoundException on "
663: + className + ". Cause: " + ex.getMessage());
664: return null;
665: } else {
666: log.error("Failed to load '" + debugContext + "' ("
667: + className + ")", ex);
668: return null;
669: }
670: }
671:
672: return clazz;
673: }
674:
675: /**
676: * Utility to essentially do Class forName and newInstance with the
677: * assumption that the environment expects failures for missing jar files
678: * and can carry on if this process fails.
679: * @param <T> The base type that we want a class to implement
680: * @param name The name for debugging purposes
681: * @param className The class to create
682: * @param impl The implementation class - what should className do?
683: * @return The new instance if it is safe or null otherwise.
684: */
685: @SuppressWarnings("unchecked")
686: public static <T> T classNewInstance(String name, String className,
687: Class<T> impl) {
688: Class<T> clazz;
689:
690: try {
691: clazz = (Class<T>) classForName(className);
692: } catch (ClassNotFoundException ex) {
693: // We expect this sometimes, hence debug
694: log.debug("Skipping '" + name
695: + "' due to ClassNotFoundException on " + className
696: + ". Cause: " + ex.getMessage());
697: return null;
698: } catch (NoClassDefFoundError ex) {
699: // We expect this sometimes, hence debug
700: log.debug("Skipping '" + name
701: + "' due to NoClassDefFoundError on " + className
702: + ". Cause: " + ex.getMessage());
703: return null;
704: } catch (TransformerFactoryConfigurationError ex) {
705: // We expect this sometimes, hence debug
706: log
707: .debug("Skipping '"
708: + name
709: + "' due to TransformerFactoryConfigurationError on "
710: + className + ". Cause: " + ex.getMessage());
711: return null;
712: }
713:
714: // Check it is of the right type
715: if (!impl.isAssignableFrom(clazz)) {
716: log.error("Class '" + clazz.getName()
717: + "' does not implement '" + impl.getName() + "'.");
718: return null;
719: }
720:
721: // Check we can create it
722: try {
723: return clazz.newInstance();
724: } catch (InstantiationException ex) {
725: log.error("InstantiationException for '" + name
726: + "' failed:", ex);
727: return null;
728: } catch (IllegalAccessException ex) {
729: log.error("IllegalAccessException for '" + name
730: + "' failed:", ex);
731: return null;
732: } catch (TransformerFactoryConfigurationError ex) {
733: log.error("TransformerFactoryConfigurationError for '"
734: + name + "' failed:", ex);
735: return null;
736: } catch (Exception ex) {
737: log.error("Failed to load creator '" + name
738: + "', classname=" + className + ": ", ex);
739: return null;
740: }
741: }
742:
743: /**
744: * InputStream closer that can cope if the input stream is null.
745: * If anything goes wrong, the errors are logged and ignored.
746: * @param in The resource to close
747: */
748: public static void close(Closeable in) {
749: if (in == null) {
750: return;
751: }
752:
753: try {
754: in.close();
755: } catch (IOException ex) {
756: log.warn(ex.getMessage(), ex);
757: }
758: }
759:
760: /**
761: * Return a List of super-classes for the given class.
762: * @param clazz the class to look up
763: * @return the List of super-classes in order going up from this one
764: */
765: public static List<Class<?>> getAllSuperclasses(Class<?> clazz) {
766: List<Class<?>> classes = new ArrayList<Class<?>>();
767:
768: Class<?> super class = clazz.getSuperclass();
769: while (super class != null) {
770: classes.add(super class);
771: super class = super class.getSuperclass();
772: }
773:
774: return classes;
775: }
776:
777: /**
778: * Return a list of all fields (whatever access status, and on whatever
779: * superclass they were defined) that can be found on this class.
780: * <p>This is like a union of {@link Class#getDeclaredFields()} which
781: * ignores and super-classes, and {@link Class#getFields()} which ignored
782: * non-public fields
783: * @param clazz The class to introspect
784: * @return The complete list of fields
785: */
786: public static Field[] getAllFields(Class<?> clazz) {
787: List<Class<?>> classes = getAllSuperclasses(clazz);
788: classes.add(clazz);
789: return getAllFields(classes);
790: }
791:
792: /**
793: * As {@link #getAllFields(Class)} but acts on a list of {@link Class}s and
794: * uses only {@link Class#getDeclaredFields()}.
795: * @param classes The list of classes to reflect on
796: * @return The complete list of fields
797: */
798: private static Field[] getAllFields(List<Class<?>> classes) {
799: Set<Field> fields = new HashSet<Field>();
800: for (Class<?> clazz : classes) {
801: fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
802: }
803:
804: return fields.toArray(new Field[fields.size()]);
805: }
806:
807: /**
808: * The log stream
809: */
810: private static final Log log = LogFactory.getLog(LocalUtil.class);
811: }
|