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 }