1 /*
2  * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @bug     5024531
26  * @summary Utility class to convert a struct-like class to a CompositeData.
27  * @author Mandy Chung
28  */
29 
30 import java.lang.reflect.*;
31 import java.util.*;
32 import javax.management.*;
33 import javax.management.openmbean.*;
34 import static javax.management.openmbean.SimpleType.*;
35 
36 /**
37  * A converter utiltiy class to automatically convert a given
38  * class to a CompositeType.
39  */
40 public class OpenTypeConverter {
41     private static final WeakHashMap<Class,OpenType> convertedTypes =
42         new WeakHashMap<Class,OpenType>();
43     private static final OpenType[] simpleTypes = {
44         BIGDECIMAL, BIGINTEGER, BOOLEAN, BYTE, CHARACTER, DATE,
45         DOUBLE, FLOAT, INTEGER, LONG, OBJECTNAME, SHORT, STRING,
46         VOID,
47     };
48 
49     static {
50         for (int i = 0; i < simpleTypes.length; i++) {
51             final OpenType t = simpleTypes[i];
52             Class c;
53             try {
54                 c = Class.forName(t.getClassName(), false,
55                                   String.class.getClassLoader());
56             } catch (ClassNotFoundException e) {
57                 // the classes that these predefined types declare must exist!
58                 assert(false);
59                 c = null; // not reached
60             }
convertedTypes.put(c, t)61             convertedTypes.put(c, t);
62 
63             if (c.getName().startsWith("java.lang.")) {
64                 try {
65                     final Field typeField = c.getField("TYPE");
66                     final Class primitiveType = (Class) typeField.get(null);
convertedTypes.put(primitiveType, t)67                     convertedTypes.put(primitiveType, t);
68                 } catch (NoSuchFieldException e) {
69                     // OK: must not be a primitive wrapper
70                 } catch (IllegalAccessException e) {
71                     // Should not reach here
72                     throw new AssertionError(e);
73                 }
74             }
75         }
76     }
77 
78     private static class InProgress extends OpenType {
79         private static final String description =
80                   "Marker to detect recursive type use -- internal use only!";
81 
InProgress()82         InProgress() throws OpenDataException {
83             super("java.lang.String", "java.lang.String", description);
84         }
85 
toString()86         public String toString() {
87             return description;
88         }
89 
hashCode()90         public int hashCode() {
91             return 0;
92         }
93 
equals(Object o)94         public boolean equals(Object o) {
95             return false;
96         }
97 
isValue(Object o)98         public boolean isValue(Object o) {
99             return false;
100         }
101     }
102     private static final OpenType inProgress;
103     static {
104         OpenType t;
105         try {
106             t = new InProgress();
107         } catch (OpenDataException e) {
108             // Should not reach here
109             throw new AssertionError(e);
110         }
111         inProgress = t;
112     }
113 
114     // Convert a class to an OpenType
toOpenType(Class c)115     public static synchronized OpenType toOpenType(Class c)
116             throws OpenDataException {
117 
118         OpenType t;
119 
120         t = convertedTypes.get(c);
121         if (t != null) {
122             if (t instanceof InProgress)
123                 throw new OpenDataException("Recursive data structure");
124             return t;
125         }
126 
127         convertedTypes.put(c, inProgress);
128 
129         if (Enum.class.isAssignableFrom(c))
130             t = STRING;
131         else if (c.isArray())
132             t = makeArrayType(c);
133         else
134             t = makeCompositeType(c);
135 
136         convertedTypes.put(c, t);
137 
138         return t;
139     }
140 
makeArrayType(Class c)141     private static OpenType makeArrayType(Class c) throws OpenDataException {
142         int dim;
143         for (dim = 0; c.isArray(); dim++)
144             c = c.getComponentType();
145         return new ArrayType(dim, toOpenType(c));
146     }
147 
makeCompositeType(Class c)148     private static OpenType makeCompositeType(Class c)
149             throws OpenDataException {
150         // Make a CompositeData containing all the getters
151         final Method[] methods = c.getMethods();
152         final List<String> names = new ArrayList<String>();
153         final List<OpenType> types = new ArrayList<OpenType>();
154 
155         /* Select public methods that look like "T getX()" or "boolean
156            isX() or hasX()", where T is not void and X is not the empty
157            string.  Exclude "Class getClass()" inherited from Object.  */
158         for (int i = 0; i < methods.length; i++) {
159             final Method method = methods[i];
160             final String name = method.getName();
161             final Class type = method.getReturnType();
162             final String rest;
163             if (name.startsWith("get"))
164                 rest = name.substring(3);
165             else if (name.startsWith("is") && type == boolean.class)
166                 rest = name.substring(2);
167             else if (name.startsWith("has") && type == boolean.class)
168                 rest = name.substring(3);
169             else
170                 continue;
171 
172             if (rest.equals("") || method.getParameterTypes().length > 0
173                 || type == void.class || rest.equals("Class"))
174                 continue;
175 
176             names.add(decapitalize(rest));
177             types.add(toOpenType(type));
178         }
179 
180         final String[] nameArray = names.toArray(new String[0]);
181         return new CompositeType(c.getName(),
182                                  c.getName(),
183                                  nameArray, // field names
184                                  nameArray, // field descriptions
185                                  types.toArray(new OpenType[0]));
186     }
187 
188     /**
189      * Utility method to take a string and convert it to normal Java variable
190      * name capitalization.  This normally means converting the first
191      * character from upper case to lower case, but in the (unusual) special
192      * case when there is more than one character and both the first and
193      * second characters are upper case, we leave it alone.
194      * <p>
195      * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
196      * as "URL".
197      *
198      * @param  name The string to be decapitalized.
199      * @return  The decapitalized version of the string.
200      */
decapitalize(String name)201     private static String decapitalize(String name) {
202         if (name == null || name.length() == 0) {
203             return name;
204         }
205         if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
206                         Character.isUpperCase(name.charAt(0))){
207             return name;
208         }
209         char chars[] = name.toCharArray();
210         chars[0] = Character.toLowerCase(chars[0]);
211         return new String(chars);
212     }
213 
214 }
215