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