3434import java .util .List ;
3535import java .util .Map ;
3636import java .util .Set ;
37+ import java .util .TreeMap ;
3738
3839/**
3940 * Class that presents the properties of a Java bean object as a map. Property
@@ -194,6 +195,10 @@ private BeanAdapter(Object bean, HashMap<Class<?>, HashMap<String, Method>> acce
194195 }
195196
196197 private static String getKey (Method method , boolean accessor ) {
198+ if (method .isBridge ()) {
199+ return null ;
200+ }
201+
197202 String methodName = method .getName ();
198203
199204 String prefix ;
@@ -295,11 +300,11 @@ public Entry<String, Object> next() {
295300 }
296301
297302 /**
298- * Adapts a value. If the value is <tt>null</tt> or an
299- * instance of one of the following types, it is returned as is:
303+ * Adapts a value. If the value is <tt>null</tt> or an instance of one of
304+ * the following types, it is returned as is:
300305 *
301306 * <ul>
302- * <li>{@link String }</li>
307+ * <li>{@link CharSequence }</li>
303308 * <li>{@link Number}</li>
304309 * <li>{@link Boolean}</li>
305310 * <li>{@link Enum}</li>
@@ -330,7 +335,7 @@ public static <T> T adapt(Object value) {
330335
331336 private static Object adapt (Object value , HashMap <Class <?>, HashMap <String , Method >> accessorCache ) {
332337 if (value == null
333- || value instanceof String
338+ || value instanceof CharSequence
334339 || value instanceof Number
335340 || value instanceof Boolean
336341 || value instanceof Enum <?>
@@ -358,7 +363,7 @@ private static Object adapt(Object value, HashMap<Class<?>, HashMap<String, Meth
358363 * values are automatically converted to <tt>0</tt> or <tt>false</tt> for
359364 * primitive argument types.</li>
360365 * <li>If the target type is {@link String}, the value is adapted via
361- * {@link String #toString()}.</li>
366+ * {@link Object #toString()}.</li>
362367 * <li>If the target type is {@link Date}, the value is coerced to a long
363368 * value and passed to {@link Date#Date(long)}.</li>
364369 * <li>If the target type is {@link LocalDate}, the value is parsed using
@@ -367,7 +372,7 @@ private static Object adapt(Object value, HashMap<Class<?>, HashMap<String, Meth
367372 * {@link LocalTime#parse(CharSequence)}.</li>
368373 * <li>If the target type is {@link LocalDateTime}, the value is parsed using
369374 * {@link LocalDateTime#parse(CharSequence)}.</li>
370- *</ul>
375+ * </ul>
371376 *
372377 * If the target type is a {@link List}, the value is wrapped in an adapter
373378 * that will adapt the list's elements. If the target type is a {@link Map},
@@ -411,11 +416,12 @@ private static <T> T adapt(Object value, Type type, HashMap<Class<?>, HashMap<St
411416 ParameterizedType parameterizedType = (ParameterizedType )type ;
412417
413418 Type rawType = parameterizedType .getRawType ();
419+ Type [] actualTypeArguments = parameterizedType .getActualTypeArguments ();
414420
415421 if (rawType == List .class ) {
416- return (T )adaptList ((List <?>)value , parameterizedType . getActualTypeArguments () [0 ], mutatorCache );
422+ return (T )adaptList ((List <?>)value , actualTypeArguments [0 ], mutatorCache );
417423 } else if (rawType == Map .class ) {
418- return (T )adaptMap ((Map <?, ?>)value , parameterizedType . getActualTypeArguments () [1 ], mutatorCache );
424+ return (T )adaptMap ((Map <?, ?>)value , actualTypeArguments [1 ], mutatorCache );
419425 } else {
420426 throw new IllegalArgumentException ();
421427 }
@@ -502,7 +508,7 @@ private static Object adapt(Object value, Class<?> type, HashMap<Class<?>, HashM
502508 } else if (type == LocalDateTime .class ) {
503509 return LocalDateTime .parse (value .toString ());
504510 } else if (value instanceof Map <?, ?>) {
505- return adapt ((Map <?, ?>)value , type , mutatorCache );
511+ return adaptBean ((Map <?, ?>)value , type , mutatorCache );
506512 } else {
507513 throw new IllegalArgumentException ();
508514 }
@@ -511,7 +517,7 @@ private static Object adapt(Object value, Class<?> type, HashMap<Class<?>, HashM
511517 }
512518 }
513519
514- private static Object adapt (Map <?, ?> map , Class <?> type , HashMap <Class <?>, HashMap <String , LinkedList <Method >>> mutatorCache ) {
520+ private static Object adaptBean (Map <?, ?> map , Class <?> type , HashMap <Class <?>, HashMap <String , LinkedList <Method >>> mutatorCache ) {
515521 if (!type .isInterface ()) {
516522 Object object ;
517523 try {
@@ -744,4 +750,151 @@ public V setValue(V value) {
744750 }
745751 };
746752 }
753+
754+ /**
755+ * Describes a type. Types are encoded as follows:
756+ *
757+ * <ul>
758+ * <li>{@link Object}: "any"</li>
759+ * <li>{@link Void} or <tt>void</tt>: "void"</li>
760+ * <li>{@link Byte} or <tt>byte</tt>: "byte"</li>
761+ * <li>{@link Short} or <tt>short</tt>: "short"</li>
762+ * <li>{@link Integer} or <tt>int</tt>: "integer"</li>
763+ * <li>{@link Long} or <tt>long</tt>: "long"</li>
764+ * <li>{@link Float} or <tt>float</tt>: "float"</li>
765+ * <li>{@link Double} or <tt>double</tt>: "double"</li>
766+ * <li>Any other type that extends {@link Number}: "number"</li>
767+ * <li>Any type that implements {@link CharSequence}: "string"</li>
768+ * <li>Any {@link Enum} type: "enum"</li>
769+ * <li>Any type that extends {@link Date}: "date"</li>
770+ * <li>{@link LocalDate}: "local-date"</li>
771+ * <li>{@link LocalTime}: "local-time"</li>
772+ * <li>{@link LocalDateTime}: "local-datetime"</li>
773+ * <li>{@link LocalDateTime}: "local-datetime"</li>
774+ * <li>{@link List}: "[<i>element description</i>]"</li>
775+ * <li>{@link Map}: "[<i>key description</i>: <i>value description</i>]"</li>
776+ * </ul>
777+ *
778+ * Otherwise, the type is assumed to be a bean and is described as follows:
779+ *
780+ * <blockquote>
781+ * {
782+ * property1: <i>property 1 description</i>,
783+ * property2: <i>property 2 description</i>,
784+ * ...
785+ * }
786+ * </blockquote>
787+ *
788+ * @param type
789+ * The type to describe.
790+ *
791+ * @param structures
792+ * A map that will be populated with descriptions of all bean types
793+ * referenced by this type.
794+ *
795+ * @return
796+ * The type's description.
797+ */
798+ public static String describe (Type type , Map <Class <?>, String > structures ) {
799+ if (type instanceof Class <?>) {
800+ return describe ((Class <?>)type , structures );
801+ } else if (type instanceof WildcardType ) {
802+ WildcardType wildcardType = (WildcardType )type ;
803+
804+ return describe (wildcardType .getUpperBounds ()[0 ], structures );
805+ } else if (type instanceof ParameterizedType ) {
806+ ParameterizedType parameterizedType = (ParameterizedType )type ;
807+
808+ Type rawType = parameterizedType .getRawType ();
809+ Type [] actualTypeArguments = parameterizedType .getActualTypeArguments ();
810+
811+ if (rawType == List .class ) {
812+ return "[" + describe (actualTypeArguments [0 ], structures ) + "]" ;
813+ } else if (rawType == Map .class ) {
814+ return "[" + describe (actualTypeArguments [0 ], structures ) + ": " + describe (actualTypeArguments [1 ], structures ) + "]" ;
815+ } else {
816+ throw new IllegalArgumentException ();
817+ }
818+ } else {
819+ throw new IllegalArgumentException ();
820+ }
821+ }
822+
823+ private static String describe (Class <?> type , Map <Class <?>, String > structures ) {
824+ if (type == Object .class ) {
825+ return "any" ;
826+ } else if (type == Void .TYPE || type == Void .class ) {
827+ return "void" ;
828+ } else if (type == Byte .TYPE || type == Byte .class ) {
829+ return "byte" ;
830+ } else if (type == Short .TYPE || type == Short .class ) {
831+ return "short" ;
832+ } else if (type == Integer .TYPE || type == Integer .class ) {
833+ return "integer" ;
834+ } else if (type == Long .TYPE || type == Long .class ) {
835+ return "long" ;
836+ } else if (type == Float .TYPE || type == Float .class ) {
837+ return "float" ;
838+ } else if (type == Double .TYPE || type == Double .class ) {
839+ return "double" ;
840+ } else if (Number .class .isAssignableFrom (type )) {
841+ return "number" ;
842+ } else if (type == Boolean .TYPE || type == Boolean .class ) {
843+ return "boolean" ;
844+ } else if (CharSequence .class .isAssignableFrom (type )) {
845+ return "string" ;
846+ } else if (Enum .class .isAssignableFrom (type )) {
847+ return "enum" ;
848+ } else if (Date .class .isAssignableFrom (type )) {
849+ return "date" ;
850+ } else if (type == LocalDate .class ) {
851+ return "local-date" ;
852+ } else if (type == LocalTime .class ) {
853+ return "local-time" ;
854+ } else if (type == LocalDateTime .class ) {
855+ return "local-datetime" ;
856+ } else {
857+ if (!structures .containsKey (type )) {
858+ Method [] methods = type .getMethods ();
859+
860+ TreeMap <String , String > properties = new TreeMap <>();
861+
862+ for (int i = 0 ; i < methods .length ; i ++) {
863+ Method method = methods [i ];
864+
865+ if (method .getDeclaringClass () == Object .class ) {
866+ continue ;
867+ }
868+
869+ String key = getKey (method , true );
870+
871+ if (key != null ) {
872+ properties .put (key , describe (method .getGenericReturnType (), structures ));
873+ }
874+ }
875+
876+ int j = 0 ;
877+
878+ StringBuilder descriptionBuilder = new StringBuilder ();
879+
880+ descriptionBuilder .append ("{\n " );
881+
882+ for (Map .Entry <String , String > entry : properties .entrySet ()) {
883+ if (j > 0 ) {
884+ descriptionBuilder .append (",\n " );
885+ }
886+
887+ descriptionBuilder .append (" " + entry .getKey () + ": " + entry .getValue ());
888+
889+ j ++;
890+ }
891+
892+ descriptionBuilder .append ("\n }" );
893+
894+ structures .put (type , descriptionBuilder .toString ());
895+ }
896+
897+ return type .getSimpleName ();
898+ }
899+ }
747900}
0 commit comments