1/*
2 * Copyright (c) 2004, 2013, 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.tools.jconsole.inspector;
27
28import java.awt.event.*;
29import java.lang.reflect.*;
30import java.math.BigDecimal;
31import java.math.BigInteger;
32import java.util.*;
33import java.util.concurrent.ExecutionException;
34import javax.management.*;
35import javax.management.openmbean.*;
36import javax.swing.*;
37import javax.swing.text.*;
38
39public class Utils {
40
41    private Utils() {
42    }
43    private static Set<Integer> tableNavigationKeys =
44            new HashSet<Integer>(Arrays.asList(new Integer[]{
45        KeyEvent.VK_TAB, KeyEvent.VK_ENTER,
46        KeyEvent.VK_HOME, KeyEvent.VK_END,
47        KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT,
48        KeyEvent.VK_UP, KeyEvent.VK_DOWN,
49        KeyEvent.VK_PAGE_UP, KeyEvent.VK_PAGE_DOWN
50    }));
51    private static final Set<Class<?>> primitiveWrappers =
52            new HashSet<Class<?>>(Arrays.asList(new Class<?>[]{
53        Byte.class, Short.class, Integer.class, Long.class,
54        Float.class, Double.class, Character.class, Boolean.class
55    }));
56    private static final Set<Class<?>> primitives = new HashSet<Class<?>>();
57    private static final Map<String, Class<?>> primitiveMap =
58            new HashMap<String, Class<?>>();
59    private static final Map<String, Class<?>> primitiveToWrapper =
60            new HashMap<String, Class<?>>();
61    private static final Set<String> editableTypes = new HashSet<String>();
62    private static final Set<Class<?>> extraEditableClasses =
63            new HashSet<Class<?>>(Arrays.asList(new Class<?>[]{
64        BigDecimal.class, BigInteger.class, Number.class,
65        String.class, ObjectName.class
66    }));
67    private static final Set<String> numericalTypes = new HashSet<String>();
68    private static final Set<String> extraNumericalTypes =
69            new HashSet<String>(Arrays.asList(new String[]{
70        BigDecimal.class.getName(), BigInteger.class.getName(),
71        Number.class.getName()
72    }));
73    private static final Set<String> booleanTypes =
74            new HashSet<String>(Arrays.asList(new String[]{
75        Boolean.TYPE.getName(), Boolean.class.getName()
76    }));
77
78    static {
79        // compute primitives/primitiveMap/primitiveToWrapper
80        for (Class<?> c : primitiveWrappers) {
81            try {
82                Field f = c.getField("TYPE");
83                Class<?> p = (Class<?>) f.get(null);
84                primitives.add(p);
85                primitiveMap.put(p.getName(), p);
86                primitiveToWrapper.put(p.getName(), c);
87            } catch (Exception e) {
88                throw new AssertionError(e);
89            }
90        }
91        // compute editableTypes
92        for (Class<?> c : primitives) {
93            editableTypes.add(c.getName());
94        }
95        for (Class<?> c : primitiveWrappers) {
96            editableTypes.add(c.getName());
97        }
98        for (Class<?> c : extraEditableClasses) {
99            editableTypes.add(c.getName());
100        }
101        // compute numericalTypes
102        for (Class<?> c : primitives) {
103            String name = c.getName();
104            if (!name.equals(Boolean.TYPE.getName())) {
105                numericalTypes.add(name);
106            }
107        }
108        for (Class<?> c : primitiveWrappers) {
109            String name = c.getName();
110            if (!name.equals(Boolean.class.getName())) {
111                numericalTypes.add(name);
112            }
113        }
114    }
115
116    /**
117     * This method returns the class matching the name className.
118     * It's used to cater for the primitive types.
119     */
120    public static Class<?> getClass(String className)
121            throws ClassNotFoundException {
122        Class<?> c;
123        if ((c = primitiveMap.get(className)) != null) {
124            return c;
125        }
126        return Class.forName(className);
127    }
128
129    /**
130     * Check if the given collection is a uniform collection of the given type.
131     */
132    public static boolean isUniformCollection(Collection<?> c, Class<?> e) {
133        if (e == null) {
134            throw new IllegalArgumentException("Null reference type");
135        }
136        if (c == null) {
137            throw new IllegalArgumentException("Null collection");
138        }
139        if (c.isEmpty()) {
140            return false;
141        }
142        for (Object o : c) {
143            if (o == null || !e.isAssignableFrom(o.getClass())) {
144                return false;
145            }
146        }
147        return true;
148    }
149
150    /**
151     * Check if the given element denotes a supported array-friendly data
152     * structure, i.e. a data structure jconsole can render as an array.
153     */
154    public static boolean canBeRenderedAsArray(Object elem) {
155        if (isSupportedArray(elem)) {
156            return true;
157        }
158        if (elem instanceof Collection) {
159            Collection<?> c = (Collection<?>) elem;
160            if (c.isEmpty()) {
161                // Empty collections of any Java type are not handled as arrays
162                //
163                return false;
164            } else {
165                // - Collections of CompositeData/TabularData are not handled
166                //   as arrays
167                // - Collections of other Java types are handled as arrays
168                //
169                return !isUniformCollection(c, CompositeData.class) &&
170                        !isUniformCollection(c, TabularData.class);
171            }
172        }
173        if (elem instanceof Map) {
174            return !(elem instanceof TabularData);
175        }
176        return false;
177    }
178
179    /**
180     * Check if the given element is an array.
181     *
182     * Multidimensional arrays are not supported.
183     *
184     * Non-empty 1-dimensional arrays of CompositeData
185     * and TabularData are not handled as arrays but as
186     * tabular data.
187     */
188    public static boolean isSupportedArray(Object elem) {
189        if (elem == null || !elem.getClass().isArray()) {
190            return false;
191        }
192        Class<?> ct = elem.getClass().getComponentType();
193        if (ct.isArray()) {
194            return false;
195        }
196        if (Array.getLength(elem) > 0 &&
197                (CompositeData.class.isAssignableFrom(ct) ||
198                TabularData.class.isAssignableFrom(ct))) {
199            return false;
200        }
201        return true;
202    }
203
204    /**
205     * This method provides a readable classname if it's an array,
206     * i.e. either the classname of the component type for arrays
207     * of java reference types or the name of the primitive type
208     * for arrays of java primitive types. Otherwise, it returns null.
209     */
210    public static String getArrayClassName(String name) {
211        String className = null;
212        if (name.startsWith("[")) {
213            int index = name.lastIndexOf('[');
214            className = name.substring(index, name.length());
215            if (className.startsWith("[L")) {
216                className = className.substring(2, className.length() - 1);
217            } else {
218                try {
219                    Class<?> c = Class.forName(className);
220                    className = c.getComponentType().getName();
221                } catch (ClassNotFoundException e) {
222                    // Should not happen
223                    throw new IllegalArgumentException(
224                            "Bad class name " + name, e);
225                }
226            }
227        }
228        return className;
229    }
230
231    /**
232     * This methods provides a readable classname. If the supplied name
233     * parameter denotes an array this method returns either the classname
234     * of the component type for arrays of java reference types or the name
235     * of the primitive type for arrays of java primitive types followed by
236     * n-times "[]" where 'n' denotes the arity of the array. Otherwise, if
237     * the supplied name doesn't denote an array it returns the same classname.
238     */
239    public static String getReadableClassName(String name) {
240        String className = getArrayClassName(name);
241        if (className == null) {
242            return name;
243        }
244        int index = name.lastIndexOf('[');
245        StringBuilder brackets = new StringBuilder(className);
246        for (int i = 0; i <= index; i++) {
247            brackets.append("[]");
248        }
249        return brackets.toString();
250    }
251
252    /**
253     * This method tells whether the type is editable
254     * (means can be created with a String or not)
255     */
256    public static boolean isEditableType(String type) {
257        return editableTypes.contains(type);
258    }
259
260    /**
261     * This method inserts a default value for the standard java types,
262     * else it inserts the text name of the expected class type.
263     * It acts to give a clue as to the input type.
264     */
265    public static String getDefaultValue(String type) {
266        if (numericalTypes.contains(type) ||
267                extraNumericalTypes.contains(type)) {
268            return "0";
269        }
270        if (booleanTypes.contains(type)) {
271            return "true";
272        }
273        type = getReadableClassName(type);
274        int i = type.lastIndexOf('.');
275        if (i > 0) {
276            return type.substring(i + 1, type.length());
277        } else {
278            return type;
279        }
280    }
281
282    /**
283     * Try to create a Java object using a one-string-param constructor.
284     */
285    public static Object newStringConstructor(String type, String param)
286            throws Exception {
287        Constructor<?> c = Utils.getClass(type).getConstructor(String.class);
288        try {
289            return c.newInstance(param);
290        } catch (InvocationTargetException e) {
291            Throwable t = e.getTargetException();
292            if (t instanceof Exception) {
293                throw (Exception) t;
294            } else {
295                throw e;
296            }
297        }
298    }
299
300    /**
301     * Try to convert a string value into a numerical value.
302     */
303    private static Number createNumberFromStringValue(String value)
304            throws NumberFormatException {
305        final String suffix = value.substring(value.length() - 1);
306        if ("L".equalsIgnoreCase(suffix)) {
307            return Long.valueOf(value.substring(0, value.length() - 1));
308        }
309        if ("F".equalsIgnoreCase(suffix)) {
310            return Float.valueOf(value.substring(0, value.length() - 1));
311        }
312        if ("D".equalsIgnoreCase(suffix)) {
313            return Double.valueOf(value.substring(0, value.length() - 1));
314        }
315        try {
316            return Integer.valueOf(value);
317        } catch (NumberFormatException e) {
318        // OK: Ignore exception...
319        }
320        try {
321            return Long.valueOf(value);
322        } catch (NumberFormatException e1) {
323        // OK: Ignore exception...
324        }
325        try {
326            return Double.valueOf(value);
327        } catch (NumberFormatException e2) {
328        // OK: Ignore exception...
329        }
330        throw new NumberFormatException("Cannot convert string value '" +
331                value + "' into a numerical value");
332    }
333
334    /**
335     * This method attempts to create an object of the given "type"
336     * using the "value" parameter.
337     * e.g. calling createObjectFromString("java.lang.Integer", "10")
338     * will return an Integer object initialized to 10.
339     */
340    public static Object createObjectFromString(String type, String value)
341            throws Exception {
342        Object result;
343        if (primitiveToWrapper.containsKey(type)) {
344            if (type.equals(Character.TYPE.getName())) {
345                result = value.charAt(0);
346            } else {
347                result = newStringConstructor(
348                        ((Class<?>) primitiveToWrapper.get(type)).getName(),
349                        value);
350            }
351        } else if (type.equals(Character.class.getName())) {
352            result = value.charAt(0);
353        } else if (Number.class.isAssignableFrom(Utils.getClass(type))) {
354            result = createNumberFromStringValue(value);
355        } else if (value == null || value.equals("null")) {
356            // hack for null value
357            result = null;
358        } else {
359            // try to create a Java object using
360            // the one-string-param constructor
361            result = newStringConstructor(type, value);
362        }
363        return result;
364    }
365
366    /**
367     * This method is responsible for converting the inputs given by the user
368     * into a useful object array for passing into a parameter array.
369     */
370    public static Object[] getParameters(XTextField[] inputs, String[] params)
371            throws Exception {
372        Object result[] = new Object[inputs.length];
373        Object userInput;
374        for (int i = 0; i < inputs.length; i++) {
375            userInput = inputs[i].getValue();
376            // if it's already a complex object, use the value
377            // else try to instantiate with string constructor
378            if (userInput instanceof XObject) {
379                result[i] = ((XObject) userInput).getObject();
380            } else {
381                result[i] = createObjectFromString(params[i].toString(),
382                        (String) userInput);
383            }
384        }
385        return result;
386    }
387
388    /**
389     * If the exception is wrapped, unwrap it.
390     */
391    public static Throwable getActualException(Throwable e) {
392        if (e instanceof ExecutionException) {
393            e = e.getCause();
394        }
395        if (e instanceof MBeanException ||
396                e instanceof RuntimeMBeanException ||
397                e instanceof RuntimeOperationsException ||
398                e instanceof ReflectionException) {
399            Throwable t = e.getCause();
400            if (t != null) {
401                return t;
402            }
403        }
404        return e;
405    }
406
407    @SuppressWarnings("serial")
408    public static class ReadOnlyTableCellEditor
409            extends DefaultCellEditor {
410
411        public ReadOnlyTableCellEditor(JTextField tf) {
412            super(tf);
413            tf.addFocusListener(new Utils.EditFocusAdapter(this));
414            tf.addKeyListener(new Utils.CopyKeyAdapter());
415        }
416    }
417
418    public static class EditFocusAdapter extends FocusAdapter {
419
420        private CellEditor editor;
421
422        public EditFocusAdapter(CellEditor editor) {
423            this.editor = editor;
424        }
425
426        @Override
427        public void focusLost(FocusEvent e) {
428            editor.stopCellEditing();
429        }
430    }
431
432    public static class CopyKeyAdapter extends KeyAdapter {
433        private static final String defaultEditorKitCopyActionName =
434                DefaultEditorKit.copyAction;
435        private static final String transferHandlerCopyActionName =
436                (String) TransferHandler.getCopyAction().getValue(Action.NAME);
437        @Override
438        public void keyPressed(KeyEvent e) {
439            // Accept "copy" key strokes
440            KeyStroke ks = KeyStroke.getKeyStroke(
441                    e.getKeyCode(), e.getModifiersEx());
442            JComponent comp = (JComponent) e.getSource();
443            for (int i = 0; i < 3; i++) {
444                InputMap im = comp.getInputMap(i);
445                Object key = im.get(ks);
446                if (defaultEditorKitCopyActionName.equals(key) ||
447                        transferHandlerCopyActionName.equals(key)) {
448                    return;
449                }
450            }
451            // Accept JTable navigation key strokes
452            if (!tableNavigationKeys.contains(e.getKeyCode())) {
453                e.consume();
454            }
455        }
456
457        @Override
458        public void keyTyped(KeyEvent e) {
459            e.consume();
460        }
461    }
462}
463