001    /*
002     * Copyright 2002-2004 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.clazz.bean;
017    
018    import java.lang.reflect.Constructor;
019    import java.util.ArrayList;
020    import java.util.Collections;
021    import java.util.HashMap;
022    import java.util.HashSet;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.Set;
027    
028    import org.apache.commons.clazz.Clazz;
029    import org.apache.commons.clazz.ClazzChangeListener;
030    import org.apache.commons.clazz.ClazzInstanceFactory;
031    import org.apache.commons.clazz.ClazzLoader;
032    import org.apache.commons.clazz.ClazzOperation;
033    import org.apache.commons.clazz.ClazzProperty;
034    
035    /**
036     * Dynamically constructed Clazz. BeanClazzes are created by invoking {@link
037     * ClazzLoader#defineClazz(String, Class, Class) clazzLoader.defineClazz (name,
038     * clazzClass, instanceClass)}.
039     * 
040     * @author <a href="mailto:scolebourne@apache.org">Stephen Colebourne</a>
041     * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a>
042     * @version $Id: BeanClazz.java 155436 2005-02-26 13:17:48Z dirkv $
043     */
044    public class BeanClazz extends Clazz {
045        private Clazz superClazz;
046        private List declaredProperties = new ArrayList();
047        private List properties = new ArrayList();
048        private Map propertyMap = new HashMap();
049        private List declaredOperations = new ArrayList();
050        private List operations = new ArrayList();
051        private Map operationMap = new HashMap();
052        private List instanceFactories;
053        private Map instanceFactoryMap = new HashMap();
054        private Class instanceClass;
055        private List subclasses = new ArrayList();
056    
057        /**
058         * Constructor for BeanClazz.
059         * @param loader
060         * @param name
061         * @param instanceClass
062         */
063        public BeanClazz(ClazzLoader loader, String name, Class instanceClass) {
064            super(loader, name);
065            this.instanceClass = instanceClass;
066        }
067    
068        public Class getInstanceClass() {
069            if (instanceClass != null) {
070                return instanceClass;
071            }
072    
073            if (superClazz instanceof BeanClazz) {
074                return ((BeanClazz) superClazz).getInstanceClass();
075            }
076    
077            return BasicBean.class;
078        }
079    
080        /**
081         * @see org.apache.commons.clazz.Clazz#getSuperclazz()
082         */
083        public Clazz getSuperclazz() {
084            return superClazz;
085        }
086    
087        public void setSuperclazz(Clazz clazz) {
088            if (superClazz != null) {
089                superClazz.removeClazzChangeListener(listener);
090            }
091            superClazz = clazz;
092            if (clazz != null) {
093                superClazz.addClazzChangeListener(listener);
094            }
095            refreshAllCaches();
096        }
097    
098        protected void refreshAllCaches() {
099            refreshPropertyCache();
100            refreshOperationCache();
101        }
102    
103        protected void refreshPropertyCache() {
104            properties = new ArrayList();
105            propertyMap = new HashMap();
106    
107            Set propertyNames = new HashSet();
108            if (superClazz != null) {
109                List superProperties = superClazz.getProperties();
110                properties.addAll(superProperties);
111                for (int i = 0; i < superProperties.size(); i++) {
112                    ClazzProperty superProperty =
113                        (ClazzProperty) superProperties.get(i);
114                    propertyNames.add(superProperty.getName());
115                }
116            }
117            for (int i = 0; i < declaredProperties.size(); i++) {
118                ClazzProperty property = (ClazzProperty) declaredProperties.get(i);
119                String name = property.getName();
120                if (!propertyNames.contains(name)) {
121                    properties.add(property);
122                }
123            }
124    
125            for (Iterator iter = properties.iterator(); iter.hasNext();) {
126                ClazzProperty property = (ClazzProperty) iter.next();
127                propertyMap.put(property.getName(), property);
128            }
129        }
130    
131        protected void refreshOperationCache() {
132            List operations = new ArrayList();
133            Set signatures = new HashSet();
134            if (superClazz != null) {
135                List superOperations = superClazz.getOperations();
136                operations.addAll(superOperations);
137                for (int i = 0; i < superOperations.size(); i++) {
138                    ClazzOperation superOperation =
139                        (ClazzOperation) superOperations.get(i);
140                    signatures.add(superOperation.getSignature());
141                }
142            }
143            for (int i = 0; i < declaredOperations.size(); i++) {
144                ClazzOperation operation =
145                    (ClazzOperation) declaredOperations.get(i);
146                String signature = operation.getSignature();
147                if (!signatures.contains(signature)) {
148                    operations.add(operation);
149                }
150            }
151        }
152    
153        /**
154         * @see org.apache.commons.clazz.Clazz#getDeclaredProperties()
155         */
156        public List getDeclaredProperties() {
157            return Collections.unmodifiableList(declaredProperties);
158        }
159    
160        public void addDeclaredProperty(ClazzProperty property) {
161            if (property.getDeclaringClazz() != this) {
162                throw new BeanClazzConfigurationException(
163                    "Property belongs to a different clazz: "
164                        + property.getDeclaringClazz().getName());
165            }
166    
167            ClazzProperty oldProperty =
168                (ClazzProperty) propertyMap.get(property.getName());
169            if (oldProperty != null) {
170                removeDeclaredProperty(oldProperty);
171            }
172    
173            declaredProperties.add(property);
174    
175            addProperty(property);
176        }
177    
178        /**
179         * Called indirectly when declared properties are manipulated.
180         */
181        protected void addProperty(ClazzProperty property) {
182            properties.add(property);
183            propertyMap.put(property.getName(), property);
184            firePropertyAdded(property);
185        }
186    
187        public void removeDeclaredProperty(ClazzProperty property) {
188            String name = property.getName();
189            property = (ClazzProperty) propertyMap.get(name);
190            if (property != null) {
191                declaredProperties.remove(property);
192                removeProperty(property);
193            }
194        }
195    
196        /**
197         * Called indirectly when declared properties are manipulated.
198         */
199        protected void removeProperty(ClazzProperty property) {
200            String name = property.getName();
201            properties.remove(property);
202            propertyMap.remove(name);
203            firePropertyRemoved(property);
204    
205            // By deleting this declared property, we may have exposed 
206            // an inherited one
207            if (superClazz != null) {
208                property = superClazz.getProperty(name);
209                if (property != null) {
210                    addProperty(property);
211                }
212            }
213        }
214    
215        /**
216         * @see org.apache.commons.clazz.Clazz#getProperties()
217         */
218        public List getProperties() {
219            return properties;
220        }
221    
222        /**
223         * @see org.apache.commons.clazz.Clazz#getProperty(java.lang.String)
224         */
225        public ClazzProperty getProperty(String name) {
226            return (ClazzProperty) propertyMap.get(name);
227        }
228    
229        /**
230         * @see org.apache.commons.clazz.Clazz#getDeclaredOperations()
231         */
232        public List getDeclaredOperations() {
233            return Collections.unmodifiableList(declaredOperations);
234        }
235    
236        public void addDeclaredOperation(ClazzOperation operation) {
237            if (operation.getDeclaringClazz() != this) {
238                throw new BeanClazzConfigurationException(
239                    "Operation belongs to a different clazz: "
240                        + operation.getDeclaringClazz().getName());
241            }
242    
243            ClazzOperation oldOperation =
244                (ClazzOperation) operationMap.get(operation.getSignature());
245            if (oldOperation != null) {
246                removeDeclaredOperation(oldOperation);
247            }
248    
249            declaredOperations.add(operation);
250    
251            addOperation(operation);
252        }
253    
254        /**
255         * Called indirectly when declared operations are manipulated.
256         */
257        protected void addOperation(ClazzOperation operation) {
258            operations.add(operation);
259            operationMap.put(operation.getSignature(), operation);
260            fireOperationAdded(operation);
261        }
262    
263        public void removeDeclaredOperation(ClazzOperation operation) {
264            String signature = operation.getSignature();
265            operation = (ClazzOperation) operationMap.get(signature);
266            if (operation != null) {
267                declaredOperations.remove(operation);
268                removeOperation(operation);
269            }
270        }
271    
272        /**
273         * Called indirectly when declared operations are manipulated.
274         */
275        protected void removeOperation(ClazzOperation operation) {
276            String signature = operation.getSignature();
277            operations.remove(operation);
278            operationMap.remove(signature);
279            fireOperationRemoved(operation);
280    
281            // By deleting this declared operation, we may have exposed
282            // an inherited one
283            if (superClazz != null) {
284                operation = superClazz.getOperation(signature);
285                if (operation != null) {
286                    addOperation(operation);
287                }
288            }
289        }
290    
291        /**
292         * @see org.apache.commons.clazz.Clazz#getOperations()
293         */
294        public List getOperations() {
295            return operations;
296        }
297    
298        /**
299         * @see org.apache.commons.clazz.Clazz#getOperation(java.lang.String)
300         */
301        public ClazzOperation getOperation(String signature) {
302            return (ClazzOperation) operationMap.get(signature);
303        }
304    
305        /**
306         * @see org.apache.commons.clazz.Clazz#getInstanceFactories()
307         */
308        public List getInstanceFactories() {
309            if (instanceFactories == null) {
310                introspectInstanceFactories();
311            }
312            return Collections.unmodifiableList(instanceFactories);
313        }
314    
315        /**
316         * @see org.apache.commons.clazz.Clazz#getInstanceFactory(java.lang.String)
317         */
318        public ClazzInstanceFactory getInstanceFactory(String signature) {
319            if (instanceFactories == null) {
320                introspectInstanceFactories();
321            }
322    
323            return (ClazzInstanceFactory) instanceFactoryMap.get(signature);
324        }
325    
326        public void addInstanceFactory(ClazzInstanceFactory factory) {
327            if (factory.getDeclaringClazz() != this) {
328                throw new BeanClazzConfigurationException(
329                    "Instance factory belongs to a different clazz: "
330                        + factory.getDeclaringClazz().getName());
331            }
332    
333            if (instanceFactories == null) {
334                introspectInstanceFactories();
335            }
336            ClazzInstanceFactory oldFactory =
337                (ClazzInstanceFactory) instanceFactoryMap.get(
338                    factory.getSignature());
339            if (oldFactory != null) {
340                removeInstanceFactory(oldFactory);
341            }
342            instanceFactories.add(factory);
343            instanceFactoryMap.put(factory.getSignature(), factory);
344            fireInstanceFactoryAdded(factory);
345        }
346    
347        public void removeInstanceFactory(ClazzInstanceFactory factory) {
348            if (instanceFactories.remove(factory)) {
349                instanceFactoryMap.remove(factory.getSignature());
350                fireInstanceFactoryRemoved(factory);
351            }
352        }
353    
354        /**
355         * Finds all constructors of the <code>instanceClass</code> that have at
356         * least one argument and the first argument is of type Clazz. Wraps
357         * each of those constructors into
358         * a {@link BeanClazzConstructorInstanceFactory
359         * BeanClazzConstructorInstanceFactory} and registers it as an
360         * InstanceFactory for this clazz.
361         */
362        private void introspectInstanceFactories() {
363            instanceFactories = new ArrayList();
364            Class instanceClass = getInstanceClass();
365            Constructor constructors[] = instanceClass.getConstructors();
366            for (int i = 0; i < constructors.length; i++) {
367                Class[] parameterTypes = constructors[i].getParameterTypes();
368                if (parameterTypes != null
369                    && parameterTypes.length >= 1
370                    && Clazz.class.isAssignableFrom(parameterTypes[0])) {
371                    addInstanceFactory(
372                        new BeanClazzConstructorInstanceFactory(
373                            this,
374                            constructors[i]));
375                }
376            }
377        }
378    
379        private ClazzChangeListener listener = new ClazzChangeListener() {
380            public void propertyAdded(Clazz clazz, ClazzProperty property) {
381                if (propertyMap.get(property.getName()) == null) {
382                    addProperty(property);
383                }
384            }
385    
386            public void propertyRemoved(Clazz clazz, ClazzProperty property) {
387                ClazzProperty declared =
388                    (ClazzProperty) propertyMap.get(property.getName());
389                if (declared != null && declared.equals(property)) {
390                    removeProperty(property);
391                }
392            }
393    
394            public void operationAdded(Clazz clazz, ClazzOperation operation) {
395                if (operationMap.get(operation.getSignature()) == null) {
396                    addOperation(operation);
397                }
398            }
399    
400            public void operationRemoved(Clazz clazz, ClazzOperation operation) {
401                ClazzOperation declared =
402                    (ClazzOperation) operationMap.get(operation.getSignature());
403                if (declared != null && declared.equals(operation)) {
404                    removeOperation(operation);
405                }
406            }
407    
408            public void instanceFactoryAdded(
409                Clazz clazz,
410                ClazzInstanceFactory property) 
411            {
412                // Ignore - factories are not inherited
413            }
414    
415            public void instanceFactoryRemoved(
416                Clazz clazz,
417                ClazzInstanceFactory property) 
418            {
419                // Ignore - factories are not inherited
420            }
421        };
422    }