001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.gbean; 019 020 import java.beans.Introspector; 021 import java.lang.reflect.InvocationTargetException; 022 import java.lang.reflect.Method; 023 import java.util.HashMap; 024 import java.util.Map; 025 import java.util.Set; 026 import java.util.List; 027 import java.util.ArrayList; 028 import java.util.Iterator; 029 030 import net.sf.cglib.reflect.FastClass; 031 import net.sf.cglib.reflect.FastMethod; 032 033 034 /** 035 * Wraps an <code>Object</code> in a <code>DynamicGBean</code> facade. 036 * 037 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $ 038 */ 039 public class DynamicGBeanDelegate implements DynamicGBean { 040 041 private static final Map<Class, Class> TYPE_LOOKUP = new HashMap<Class, Class>(); 042 static { 043 TYPE_LOOKUP.put(Byte.class, byte.class); 044 TYPE_LOOKUP.put(Integer.class, int.class); 045 TYPE_LOOKUP.put(Short.class, short.class); 046 TYPE_LOOKUP.put(Long.class, long.class); 047 TYPE_LOOKUP.put(Float.class, float.class); 048 TYPE_LOOKUP.put(Double.class, double.class); 049 TYPE_LOOKUP.put(Boolean.class, boolean.class); 050 TYPE_LOOKUP.put(Character.class, char.class); 051 } 052 053 protected final Map getters = new HashMap(); 054 protected final Map setters = new HashMap(); 055 protected final Map operations = new HashMap(); 056 private Class targetClass; 057 058 public void addAll(Object target) { 059 this.targetClass = target.getClass(); 060 Method[] methods = targetClass.getMethods(); 061 for (int i = 0; i < methods.length; i++) { 062 Method method = methods[i]; 063 if (isGetter(method)) { 064 addGetter(target, method); 065 } else if (isSetter(method)) { 066 addSetter(target, method); 067 } else { 068 addOperation(target, method); 069 } 070 } 071 } 072 073 public void addGetter(Object target, Method method) { 074 String name = method.getName(); 075 if (name.startsWith("get")) { 076 addGetter(name.substring(3), target, method); 077 } else if (name.startsWith("is")) { 078 addGetter(name.substring(2), target, method); 079 } else { 080 throw new IllegalArgumentException("Method name must start with 'get' or 'is' " + method); 081 } 082 } 083 084 public void addGetter(String name, Object target, Method method) { 085 if (!(method.getParameterTypes().length == 0 && method.getReturnType() != Void.TYPE)) { 086 throw new IllegalArgumentException("Method must take no parameters and return a value " + method); 087 } 088 getters.put(name, new Operation(target, method)); 089 // we want to be user-friendly so we put the attribute name in 090 // the Map in both lower-case and upper-case 091 getters.put(Introspector.decapitalize(name), new Operation(target, method)); 092 } 093 094 public void addSetter(Object target, Method method) { 095 if (!method.getName().startsWith("set")) { 096 throw new IllegalArgumentException("Method name must start with 'set' " + method); 097 } 098 addSetter(method.getName().substring(3), target, method); 099 } 100 101 public void addSetter(String name, Object target, Method method) { 102 if (!(method.getParameterTypes().length == 1 && method.getReturnType() == Void.TYPE)) { 103 throw new IllegalArgumentException("Method must take one parameter and not return anything " + method); 104 } 105 Class type = method.getParameterTypes()[0]; 106 Operation operation = new Operation(target, method); 107 addSetter(name, type, operation); 108 // we want to be user-friendly so we put the attribute name in 109 // the Map in both lower-case and upper-case 110 addSetter(Introspector.decapitalize(name), type, operation); 111 } 112 113 private void addSetter(String name, Class type, Operation operation) { 114 Map<Class, Operation> operations = (Map<Class, Operation>)setters.get(name); 115 if (operations == null) { 116 operations = new HashMap<Class, Operation>(); 117 setters.put(name, operations); 118 } 119 operations.put(type, operation); 120 } 121 122 public void addOperation(Object target, Method method) { 123 Class[] parameters = method.getParameterTypes(); 124 String[] types = new String[parameters.length]; 125 for (int i = 0; i < parameters.length; i++) { 126 types[i] = parameters[i].getName(); 127 } 128 GOperationSignature key = new GOperationSignature(method.getName(), types); 129 operations.put(key, new Operation(target, method)); 130 } 131 132 private boolean isGetter(Method method) { 133 String name = method.getName(); 134 return (name.startsWith("get") || name.startsWith("is")) && 135 method.getParameterTypes().length == 0 136 && method.getReturnType() != Void.TYPE; 137 } 138 139 private boolean isSetter(Method method) { 140 return method.getName().startsWith("set") && 141 method.getParameterTypes().length == 1 && 142 method.getReturnType() == Void.TYPE; 143 } 144 145 public Object getAttribute(String name) throws Exception { 146 Operation operation = (Operation) getters.get(name); 147 if (operation == null) { 148 throw new IllegalArgumentException(targetClass.getName() + ": no getter for " + name); 149 } 150 return operation.invoke(null); 151 } 152 153 public void setAttribute(String name, Object value) throws Exception { 154 Map<Class, Operation> operations = (Map<Class, Operation>)setters.get(name); 155 if (operations == null) { 156 throw new IllegalArgumentException(targetClass.getName() + ": no setters for " + name); 157 } 158 Operation operation = null; 159 if (operations.size() == 1) { 160 operation = operations.values().iterator().next(); 161 } else if (value == null) { 162 // TODO: use better algorithm? 163 operation = operations.values().iterator().next(); 164 } else if (value != null) { 165 Class valueType = value.getClass(); 166 // lookup using the specific type 167 operation = operations.get(valueType); 168 if (operation == null) { 169 // if not found, check all setters if they accept the given type 170 operation = findOperation(operations, valueType); 171 if (operation == null && TYPE_LOOKUP.containsKey(valueType)) { 172 // if not found, check all setters if they accept the primitive type 173 operation = findOperation(operations, TYPE_LOOKUP.get(valueType)); 174 } 175 if (operation == null) { 176 throw new IllegalArgumentException(targetClass.getName() + ": no setter for " + name + " of type " + valueType); 177 } 178 } 179 } 180 181 operation.invoke(new Object[]{value}); 182 } 183 184 private Operation findOperation(Map<Class, Operation> operations, Class type) { 185 for (Map.Entry<Class, Operation> entry : operations.entrySet()) { 186 if (entry.getKey().isAssignableFrom(type)) { 187 return entry.getValue(); 188 } 189 } 190 return null; 191 } 192 193 public Object invoke(String name, Object[] arguments, String[] types) throws Exception { 194 GOperationSignature signature = new GOperationSignature(name, types); 195 Operation operation = (Operation) operations.get(signature); 196 if (operation == null) { 197 throw new IllegalArgumentException(targetClass.getName() + ": no operation " + signature); 198 } 199 return operation.invoke(arguments); 200 } 201 202 /** 203 * Gets all properties (with both a getter and setter), in the form of 204 * propertyName 205 */ 206 public String[] getProperties() { 207 Set one = getters.keySet(); 208 Set two = setters.keySet(); 209 List out = new ArrayList(); 210 for (Iterator it = one.iterator(); it.hasNext();) { 211 String name = (String) it.next(); 212 if(Character.isLowerCase(name.charAt(0)) && two.contains(name)) { 213 out.add(name); 214 } 215 } 216 return (String[]) out.toArray(new String[out.size()]); 217 } 218 219 public Class getPropertyType(String name) { 220 Operation oper = (Operation) getters.get(name); 221 return oper.method.getReturnType(); 222 } 223 224 protected static class Operation { 225 private final Object target; 226 private final FastMethod method; 227 228 public Operation(Object target, Method method) { 229 assert target != null; 230 assert method != null; 231 this.target = target; 232 this.method = FastClass.create(target.getClass()).getMethod(method); 233 } 234 235 public Object invoke(Object[] arguments) throws Exception { 236 try { 237 return method.invoke(target, arguments); 238 } catch (InvocationTargetException e) { 239 Throwable targetException = e.getTargetException(); 240 if (targetException instanceof Exception) { 241 throw (Exception) targetException; 242 } else if (targetException instanceof Error) { 243 throw (Error) targetException; 244 } 245 throw e; 246 } 247 } 248 } 249 }