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    }