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