1 /* 2 * Copyright (c) 2005, 2008, 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 javax.management.openmbean; 27 28 import com.sun.jmx.mbeanserver.MXBeanLookup; 29 import com.sun.jmx.mbeanserver.MXBeanMapping; 30 import com.sun.jmx.mbeanserver.MXBeanMappingFactory; 31 import com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory; 32 import java.lang.reflect.InvocationHandler; 33 import java.lang.reflect.Method; 34 import java.lang.reflect.Proxy; 35 36 /** 37 <p>An {@link InvocationHandler} that forwards getter methods to a 38 {@link CompositeData}. If you have an interface that contains 39 only getter methods (such as {@code String getName()} or 40 {@code boolean isActive()}) then you can use this class in 41 conjunction with the {@link Proxy} class to produce an implementation 42 of the interface where each getter returns the value of the 43 corresponding item in a {@code CompositeData}.</p> 44 45 <p>For example, suppose you have an interface like this: 46 47 <blockquote> 48 <pre> 49 public interface NamedNumber { 50 public int getNumber(); 51 public String getName(); 52 } 53 </pre> 54 </blockquote> 55 56 and a {@code CompositeData} constructed like this: 57 58 <blockquote> 59 <pre> 60 CompositeData cd = 61 new {@link CompositeDataSupport}( 62 someCompositeType, 63 new String[] {"number", "name"}, 64 new Object[] {<b>5</b>, "five"} 65 ); 66 </pre> 67 </blockquote> 68 69 then you can construct an object implementing {@code NamedNumber} 70 and backed by the object {@code cd} like this: 71 72 <blockquote> 73 <pre> 74 InvocationHandler handler = 75 new CompositeDataInvocationHandler(cd); 76 NamedNumber nn = (NamedNumber) 77 Proxy.newProxyInstance(NamedNumber.class.getClassLoader(), 78 new Class[] {NamedNumber.class}, 79 handler); 80 </pre> 81 </blockquote> 82 83 A call to {@code nn.getNumber()} will then return <b>5</b>.</p> 84 85 <p>If the first letter of the property defined by a getter is a 86 capital, then this handler will look first for an item in the 87 {@code CompositeData} beginning with a capital, then, if that is 88 not found, for an item beginning with the corresponding lowercase 89 letter or code point. For a getter called {@code getNumber()}, the 90 handler will first look for an item called {@code Number}, then for 91 {@code number}. If the getter is called {@code getnumber()}, then 92 the item must be called {@code number}.</p> 93 94 <p>If the method given to {@link #invoke invoke} is the method 95 {@code boolean equals(Object)} inherited from {@code Object}, then 96 it will return true if and only if the argument is a {@code Proxy} 97 whose {@code InvocationHandler} is also a {@code 98 CompositeDataInvocationHandler} and whose backing {@code 99 CompositeData} is equal (not necessarily identical) to this 100 object's. If the method given to {@code invoke} is the method 101 {@code int hashCode()} inherited from {@code Object}, then it will 102 return a value that is consistent with this definition of {@code 103 equals}: if two objects are equal according to {@code equals}, then 104 they will have the same {@code hashCode}.</p> 105 106 @since 1.6 107 */ 108 public class CompositeDataInvocationHandler implements InvocationHandler { 109 /** 110 <p>Construct a handler backed by the given {@code 111 CompositeData}.</p> 112 113 @param compositeData the {@code CompositeData} that will supply 114 information to getters. 115 116 @throws IllegalArgumentException if {@code compositeData} 117 is null. 118 */ 119 public CompositeDataInvocationHandler(CompositeData compositeData) { 120 this(compositeData, null); 121 } 122 123 /** 124 <p>Construct a handler backed by the given {@code 125 CompositeData}.</p> 126 127 @param compositeData the {@code CompositeData} that will supply 128 information to getters. 129 130 @throws IllegalArgumentException if {@code compositeData} 131 is null. 132 */ 133 CompositeDataInvocationHandler(CompositeData compositeData, 134 MXBeanLookup lookup) { 135 if (compositeData == null) 136 throw new IllegalArgumentException("compositeData"); 137 this.compositeData = compositeData; 138 this.lookup = lookup; 139 } 140 141 /** 142 Return the {@code CompositeData} that was supplied to the 143 constructor. 144 @return the {@code CompositeData} that this handler is backed 145 by. This is never null. 146 */ 147 public CompositeData getCompositeData() { 148 assert compositeData != null; 149 return compositeData; 150 } 151 152 public Object invoke(Object proxy, Method method, Object[] args) 153 throws Throwable { 154 final String methodName = method.getName(); 155 156 // Handle the methods from java.lang.Object 157 if (method.getDeclaringClass() == Object.class) { 158 if (methodName.equals("toString") && args == null) 159 return "Proxy[" + compositeData + "]"; 160 else if (methodName.equals("hashCode") && args == null) 161 return compositeData.hashCode() + 0x43444948; 162 else if (methodName.equals("equals") && args.length == 1 163 && method.getParameterTypes()[0] == Object.class) 164 return equals(proxy, args[0]); 165 else { 166 /* Either someone is calling invoke by hand, or 167 it is a non-final method from Object overriden 168 by the generated Proxy. At the time of writing, 169 the only non-final methods in Object that are not 170 handled above are finalize and clone, and these 171 are not overridden in generated proxies. */ 172 return method.invoke(this, args); 173 } 174 } 175 176 String propertyName = DefaultMXBeanMappingFactory.propertyName(method); 177 if (propertyName == null) { 178 throw new IllegalArgumentException("Method is not getter: " + 179 method.getName()); 180 } 181 Object openValue; 182 if (compositeData.containsKey(propertyName)) 183 openValue = compositeData.get(propertyName); 184 else { 185 String decap = DefaultMXBeanMappingFactory.decapitalize(propertyName); 186 if (compositeData.containsKey(decap)) 187 openValue = compositeData.get(decap); 188 else { 189 final String msg = 190 "No CompositeData item " + propertyName + 191 (decap.equals(propertyName) ? "" : " or " + decap) + 192 " to match " + methodName; 193 throw new IllegalArgumentException(msg); 194 } 195 } 196 MXBeanMapping mapping = 197 MXBeanMappingFactory.DEFAULT.mappingForType(method.getGenericReturnType(), 198 MXBeanMappingFactory.DEFAULT); 199 return mapping.fromOpenValue(openValue); 200 } 201 202 /* This method is called when equals(Object) is 203 * called on our proxy and hence forwarded to us. For example, if we 204 * are a proxy for an interface like this: 205 * public interface GetString { 206 * public String string(); 207 * } 208 * then we must compare equal to another CompositeDataInvocationHandler 209 * proxy for the same interface and where string() returns the same value. 210 * 211 * You might think that we should also compare equal to another 212 * object that implements GetString directly rather than using 213 * Proxy, provided that its string() returns the same result as 214 * ours, and in fact an earlier version of this class did that (by 215 * converting the other object into a CompositeData and comparing 216 * that with ours). But in fact that doesn't make a great deal of 217 * sense because there's absolutely no guarantee that the 218 * resulting equals would be reflexive (otherObject.equals(this) 219 * might be false even if this.equals(otherObject) is true), and, 220 * especially, there's no way we could generate a hashCode() that 221 * would be equal to otherObject.hashCode() when 222 * this.equals(otherObject), because we don't know how 223 * otherObject.hashCode() is computed. 224 */ 225 private boolean equals(Object proxy, Object other) { 226 if (other == null) 227 return false; 228 229 final Class<?> proxyClass = proxy.getClass(); 230 final Class<?> otherClass = other.getClass(); 231 if (proxyClass != otherClass) 232 return false; 233 InvocationHandler otherih = Proxy.getInvocationHandler(other); 234 if (!(otherih instanceof CompositeDataInvocationHandler)) 235 return false; 236 CompositeDataInvocationHandler othercdih = 237 (CompositeDataInvocationHandler) otherih; 238 return compositeData.equals(othercdih.compositeData); 239 } 240 241 private final CompositeData compositeData; 242 private final MXBeanLookup lookup; 243 }