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
30import java.lang.reflect.*;
31import java.util.*;
32import javax.management.*;
33import javax.management.openmbean.*;
34import static javax.management.openmbean.SimpleType.*;
35
36/**
37 * A converter utiltiy class to automatically convert a given
38 * class to a CompositeType.
39 */
40public 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            }
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);
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
82        InProgress() throws OpenDataException {
83            super("java.lang.String", "java.lang.String", description);
84        }
85
86        public String toString() {
87            return description;
88        }
89
90        public int hashCode() {
91            return 0;
92        }
93
94        public boolean equals(Object o) {
95            return false;
96        }
97
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
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
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
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     */
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