1 /*
2  * Copyright (c) 2005, 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 
26 package com.sun.jmx.mbeanserver;
27 
28 import static com.sun.jmx.mbeanserver.Util.*;
29 
30 import java.lang.reflect.Method;
31 import java.lang.reflect.Modifier;
32 import java.security.AccessController;
33 import java.util.Arrays;
34 import java.util.Comparator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import javax.management.NotCompliantMBeanException;
39 
40 /**
41  * <p>An analyzer for a given MBean interface.  The analyzer can
42  * be for Standard MBeans or MXBeans, depending on the MBeanIntrospector
43  * passed at construction.
44  *
45  * <p>The analyzer can
46  * visit the attributes and operations of the interface, calling
47  * a caller-supplied visitor method for each one.</p>
48  *
49  * @param <M> Method or ConvertingMethod according as this is a
50  * Standard MBean or an MXBean.
51  *
52  * @since 1.6
53  */
54 class MBeanAnalyzer<M> {
55     static interface MBeanVisitor<M> {
visitAttribute(String attributeName, M getter, M setter)56         public void visitAttribute(String attributeName,
57                 M getter,
58                 M setter);
visitOperation(String operationName, M operation)59         public void visitOperation(String operationName,
60                 M operation);
61     }
62 
visit(MBeanVisitor<M> visitor)63     void visit(MBeanVisitor<M> visitor) {
64         // visit attributes
65         for (Map.Entry<String, AttrMethods<M>> entry : attrMap.entrySet()) {
66             String name = entry.getKey();
67             AttrMethods<M> am = entry.getValue();
68             visitor.visitAttribute(name, am.getter, am.setter);
69         }
70 
71         // visit operations
72         for (Map.Entry<String, List<M>> entry : opMap.entrySet()) {
73             for (M m : entry.getValue())
74                 visitor.visitOperation(entry.getKey(), m);
75         }
76     }
77 
78     /* Map op name to method */
79     private Map<String, List<M>> opMap = newInsertionOrderMap();
80     /* Map attr name to getter and/or setter */
81     private Map<String, AttrMethods<M>> attrMap = newInsertionOrderMap();
82 
83     private static class AttrMethods<M> {
84         M getter;
85         M setter;
86     }
87 
88     /**
89      * <p>Return an MBeanAnalyzer for the given MBean interface and
90      * MBeanIntrospector.  Calling this method twice with the same
91      * parameters may return the same object or two different but
92      * equivalent objects.
93      */
94     // Currently it's two different but equivalent objects.  This only
95     // really impacts proxy generation.  For MBean creation, the
96     // cached PerInterface object for an MBean interface means that
97     // an analyzer will not be recreated for a second MBean using the
98     // same interface.
analyzer(Class<?> mbeanType, MBeanIntrospector<M> introspector)99     static <M> MBeanAnalyzer<M> analyzer(Class<?> mbeanType,
100             MBeanIntrospector<M> introspector)
101             throws NotCompliantMBeanException {
102         return new MBeanAnalyzer<M>(mbeanType, introspector);
103     }
104 
MBeanAnalyzer(Class<?> mbeanType, MBeanIntrospector<M> introspector)105     private MBeanAnalyzer(Class<?> mbeanType,
106             MBeanIntrospector<M> introspector)
107             throws NotCompliantMBeanException {
108         if (!mbeanType.isInterface()) {
109             throw new NotCompliantMBeanException("Not an interface: " +
110                     mbeanType.getName());
111         } else if (!Modifier.isPublic(mbeanType.getModifiers()) &&
112                    !Introspector.ALLOW_NONPUBLIC_MBEAN) {
113             throw new NotCompliantMBeanException("Interface is not public: " +
114                 mbeanType.getName());
115         }
116 
117         try {
118             initMaps(mbeanType, introspector);
119         } catch (Exception x) {
120             throw Introspector.throwException(mbeanType,x);
121         }
122     }
123 
124     // Introspect the mbeanInterface and initialize this object's maps.
125     //
initMaps(Class<?> mbeanType, MBeanIntrospector<M> introspector)126     private void initMaps(Class<?> mbeanType,
127             MBeanIntrospector<M> introspector) throws Exception {
128         final List<Method> methods1 = introspector.getMethods(mbeanType);
129         final List<Method> methods = eliminateCovariantMethods(methods1);
130 
131         /* Run through the methods to detect inconsistencies and to enable
132            us to give getter and setter together to visitAttribute. */
133         for (Method m : methods) {
134             final String name = m.getName();
135             final int nParams = m.getParameterTypes().length;
136 
137             final M cm = introspector.mFrom(m);
138 
139             String attrName = "";
140             if (name.startsWith("get"))
141                 attrName = name.substring(3);
142             else if (name.startsWith("is")
143             && m.getReturnType() == boolean.class)
144                 attrName = name.substring(2);
145 
146             if (attrName.length() != 0 && nParams == 0
147                     && m.getReturnType() != void.class) {
148                 // It's a getter
149                 // Check we don't have both isX and getX
150                 AttrMethods<M> am = attrMap.get(attrName);
151                 if (am == null)
152                     am = new AttrMethods<M>();
153                 else {
154                     if (am.getter != null) {
155                         final String msg = "Attribute " + attrName +
156                                 " has more than one getter";
157                         throw new NotCompliantMBeanException(msg);
158                     }
159                 }
160                 am.getter = cm;
161                 attrMap.put(attrName, am);
162             } else if (name.startsWith("set") && name.length() > 3
163                     && nParams == 1 &&
164                     m.getReturnType() == void.class) {
165                 // It's a setter
166                 attrName = name.substring(3);
167                 AttrMethods<M> am = attrMap.get(attrName);
168                 if (am == null)
169                     am = new AttrMethods<M>();
170                 else if (am.setter != null) {
171                     final String msg = "Attribute " + attrName +
172                             " has more than one setter";
173                     throw new NotCompliantMBeanException(msg);
174                 }
175                 am.setter = cm;
176                 attrMap.put(attrName, am);
177             } else {
178                 // It's an operation
179                 List<M> cms = opMap.get(name);
180                 if (cms == null)
181                     cms = newList();
182                 cms.add(cm);
183                 opMap.put(name, cms);
184             }
185         }
186         /* Check that getters and setters are consistent. */
187         for (Map.Entry<String, AttrMethods<M>> entry : attrMap.entrySet()) {
188             AttrMethods<M> am = entry.getValue();
189             if (!introspector.consistent(am.getter, am.setter)) {
190                 final String msg = "Getter and setter for " + entry.getKey() +
191                         " have inconsistent types";
192                 throw new NotCompliantMBeanException(msg);
193             }
194         }
195     }
196 
197     /**
198      * A comparator that defines a total order so that methods have the
199      * same name and identical signatures appear next to each others.
200      * The methods are sorted in such a way that methods which
201      * override each other will sit next to each other, with the
202      * overridden method first - e.g. Object getFoo() is placed before
203      * Integer getFoo(). This makes it possible to determine whether
204      * a method overrides another one simply by looking at the method(s)
205      * that precedes it in the list. (see eliminateCovariantMethods).
206      **/
207     private static class MethodOrder implements Comparator<Method> {
compare(Method a, Method b)208         public int compare(Method a, Method b) {
209             final int cmp = a.getName().compareTo(b.getName());
210             if (cmp != 0) return cmp;
211             final Class<?>[] aparams = a.getParameterTypes();
212             final Class<?>[] bparams = b.getParameterTypes();
213             if (aparams.length != bparams.length)
214                 return aparams.length - bparams.length;
215             if (!Arrays.equals(aparams, bparams)) {
216                 return Arrays.toString(aparams).
217                         compareTo(Arrays.toString(bparams));
218             }
219             final Class<?> aret = a.getReturnType();
220             final Class<?> bret = b.getReturnType();
221             if (aret == bret) return 0;
222 
223             // Super type comes first: Object, Number, Integer
224             if (aret.isAssignableFrom(bret))
225                 return -1;
226             return +1;      // could assert bret.isAssignableFrom(aret)
227         }
228         public final static MethodOrder instance = new MethodOrder();
229     }
230 
231 
232     /* Eliminate methods that are overridden with a covariant return type.
233        Reflection will return both the original and the overriding method
234        but only the overriding one is of interest.  We return the methods
235        in the same order they arrived in.  This isn't required by the spec
236        but existing code may depend on it and users may be used to seeing
237        operations or attributes appear in a particular order.
238 
239        Because of the way this method works, if the same Method appears
240        more than once in the given List then it will be completely deleted!
241        So don't do that.  */
242     static List<Method>
eliminateCovariantMethods(List<Method> startMethods)243             eliminateCovariantMethods(List<Method> startMethods) {
244         // We are assuming that you never have very many methods with the
245         // same name, so it is OK to use algorithms that are quadratic
246         // in the number of methods with the same name.
247 
248         final int len = startMethods.size();
249         final Method[] sorted = startMethods.toArray(new Method[len]);
250         Arrays.sort(sorted,MethodOrder.instance);
251         final Set<Method> overridden = newSet();
252         for (int i=1;i<len;i++) {
253             final Method m0 = sorted[i-1];
254             final Method m1 = sorted[i];
255 
256             // Methods that don't have the same name can't override each other
257             if (!m0.getName().equals(m1.getName())) continue;
258 
259             // Methods that have the same name and same signature override
260             // each other. In that case, the second method overrides the first,
261             // due to the way we have sorted them in MethodOrder.
262             if (Arrays.equals(m0.getParameterTypes(),
263                     m1.getParameterTypes())) {
264                 if (!overridden.add(m0))
265                     throw new RuntimeException("Internal error: duplicate Method");
266             }
267         }
268 
269         final List<Method> methods = newList(startMethods);
270         methods.removeAll(overridden);
271         return methods;
272     }
273 
274 
275 }
276