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