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: 476049 $ $Date: 2006-11-16 23:35:17 -0500 (Thu, 16 Nov 2006) $
038     */
039    public class DynamicGBeanDelegate implements DynamicGBean {
040        protected final Map getters = new HashMap();
041        protected final Map setters = new HashMap();
042        protected final Map operations = new HashMap();
043        private Class targetClass;
044    
045        public void addAll(Object target) {
046            this.targetClass = target.getClass();
047            Method[] methods = targetClass.getMethods();
048            for (int i = 0; i < methods.length; i++) {
049                Method method = methods[i];
050                if (isGetter(method)) {
051                    addGetter(target, method);
052                } else if (isSetter(method)) {
053                    addSetter(target, method);
054                } else {
055                    addOperation(target, method);
056                }
057            }
058        }
059    
060        public void addGetter(Object target, Method method) {
061            String name = method.getName();
062            if (name.startsWith("get")) {
063                addGetter(name.substring(3), target, method);
064            } else if (name.startsWith("is")) {
065                addGetter(name.substring(2), target, method);
066            } else {
067                throw new IllegalArgumentException("Method name must start with 'get' or 'is' " + method);
068            }
069        }
070    
071        public void addGetter(String name, Object target, Method method) {
072            if (!(method.getParameterTypes().length == 0 && method.getReturnType() != Void.TYPE)) {
073                throw new IllegalArgumentException("Method must take no parameters and return a value " + method);
074            }
075            getters.put(name, new Operation(target, method));
076            // we want to be user-friendly so we put the attribute name in
077            // the Map in both lower-case and upper-case
078            getters.put(Introspector.decapitalize(name), new Operation(target, method));
079        }
080    
081        public void addSetter(Object target, Method method) {
082            if (!method.getName().startsWith("set")) {
083                throw new IllegalArgumentException("Method name must start with 'set' " + method);
084            }
085            addSetter(method.getName().substring(3), target, method);
086        }
087    
088        public void addSetter(String name, Object target, Method method) {
089            if (!(method.getParameterTypes().length == 1 && method.getReturnType() == Void.TYPE)) {
090                throw new IllegalArgumentException("Method must take one parameter and not return anything " + method);
091            }
092            setters.put(name, new Operation(target, method));
093            // we want to be user-friendly so we put the attribute name in
094            // the Map in both lower-case and upper-case
095            setters.put(Introspector.decapitalize(name), new Operation(target, method));
096        }
097    
098        public void addOperation(Object target, Method method) {
099            Class[] parameters = method.getParameterTypes();
100            String[] types = new String[parameters.length];
101            for (int i = 0; i < parameters.length; i++) {
102                types[i] = parameters[i].getName();
103            }
104            GOperationSignature key = new GOperationSignature(method.getName(), types);
105            operations.put(key, new Operation(target, method));
106        }
107    
108        private boolean isGetter(Method method) {
109            String name = method.getName();
110            return (name.startsWith("get") || name.startsWith("is")) &&
111                    method.getParameterTypes().length == 0
112                    && method.getReturnType() != Void.TYPE;
113        }
114    
115        private boolean isSetter(Method method) {
116            return method.getName().startsWith("set") &&
117                    method.getParameterTypes().length == 1 &&
118                    method.getReturnType() == Void.TYPE;
119        }
120    
121        public Object getAttribute(String name) throws Exception {
122            Operation operation = (Operation) getters.get(name);
123            if (operation == null) {
124                throw new IllegalArgumentException(targetClass.getName() + ": no getter for " + name);
125            }
126            return operation.invoke(null);
127        }
128    
129        public void setAttribute(String name, Object value) throws Exception {
130            Operation operation = (Operation) setters.get(name);
131            if (operation == null) {
132                throw new IllegalArgumentException(targetClass.getName() + ": no setter for " + name);
133            }
134            operation.invoke(new Object[]{value});
135        }
136    
137        public Object invoke(String name, Object[] arguments, String[] types) throws Exception {
138            GOperationSignature signature = new GOperationSignature(name, types);
139            Operation operation = (Operation) operations.get(signature);
140            if (operation == null) {
141                throw new IllegalArgumentException(targetClass.getName() + ": no operation " + signature);
142            }
143            return operation.invoke(arguments);
144        }
145    
146        /**
147         * Gets all properties (with both a getter and setter), in the form of
148         * propertyName
149         */ 
150        public String[] getProperties() {
151            Set one = getters.keySet();
152            Set two = setters.keySet();
153            List out = new ArrayList();
154            for (Iterator it = one.iterator(); it.hasNext();) {
155                String name = (String) it.next();
156                if(Character.isLowerCase(name.charAt(0)) && two.contains(name)) {
157                    out.add(name);
158                }
159            }
160            return (String[]) out.toArray(new String[out.size()]);
161        }
162    
163        public Class getPropertyType(String name) {
164            Operation oper = (Operation) getters.get(name);
165            return oper.method.getReturnType();
166        }
167    
168        protected static class Operation {
169            private final Object target;
170            private final FastMethod method;
171    
172            public Operation(Object target, Method method) {
173                assert target != null;
174                assert method != null;
175                this.target = target;
176                this.method = FastClass.create(target.getClass()).getMethod(method);
177            }
178    
179            public Object invoke(Object[] arguments) throws Exception {
180                try {
181                    return method.invoke(target, arguments);
182                } catch (InvocationTargetException e) {
183                    Throwable targetException = e.getTargetException();
184                    if (targetException instanceof Exception) {
185                        throw (Exception) targetException;
186                    } else if (targetException instanceof Error) {
187                        throw (Error) targetException;
188                    }
189                    throw e;
190                }
191            }
192        }
193    }