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 }