Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
XMLIntrospector |
|
| 2.1704545454545454;2.17 |
1 | package org.apache.commons.betwixt; |
|
2 | ||
3 | /* |
|
4 | * Copyright 2001-2005 The Apache Software Foundation. |
|
5 | * |
|
6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
|
7 | * you may not use this file except in compliance with the License. |
|
8 | * You may obtain a copy of the License at |
|
9 | * |
|
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
|
11 | * |
|
12 | * Unless required by applicable law or agreed to in writing, software |
|
13 | * distributed under the License is distributed on an "AS IS" BASIS, |
|
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
15 | * See the License for the specific language governing permissions and |
|
16 | * limitations under the License. |
|
17 | */ |
|
18 | ||
19 | import java.beans.BeanDescriptor; |
|
20 | import java.beans.BeanInfo; |
|
21 | import java.beans.IntrospectionException; |
|
22 | import java.beans.Introspector; |
|
23 | import java.beans.PropertyDescriptor; |
|
24 | import java.io.IOException; |
|
25 | import java.lang.reflect.Method; |
|
26 | import java.net.URL; |
|
27 | import java.util.ArrayList; |
|
28 | import java.util.HashMap; |
|
29 | import java.util.Iterator; |
|
30 | import java.util.List; |
|
31 | import java.util.Map; |
|
32 | import java.util.Set; |
|
33 | ||
34 | import org.apache.commons.beanutils.DynaBean; |
|
35 | import org.apache.commons.beanutils.DynaClass; |
|
36 | import org.apache.commons.beanutils.DynaProperty; |
|
37 | import org.apache.commons.betwixt.digester.MultiMappingBeanInfoDigester; |
|
38 | import org.apache.commons.betwixt.digester.XMLBeanInfoDigester; |
|
39 | import org.apache.commons.betwixt.digester.XMLIntrospectorHelper; |
|
40 | import org.apache.commons.betwixt.expression.CollectionUpdater; |
|
41 | import org.apache.commons.betwixt.expression.EmptyExpression; |
|
42 | import org.apache.commons.betwixt.expression.IteratorExpression; |
|
43 | import org.apache.commons.betwixt.expression.MapEntryAdder; |
|
44 | import org.apache.commons.betwixt.expression.MethodUpdater; |
|
45 | import org.apache.commons.betwixt.expression.StringExpression; |
|
46 | import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry; |
|
47 | import org.apache.commons.betwixt.registry.PolymorphicReferenceResolver; |
|
48 | import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry; |
|
49 | import org.apache.commons.betwixt.strategy.ClassNormalizer; |
|
50 | import org.apache.commons.betwixt.strategy.DefaultNameMapper; |
|
51 | import org.apache.commons.betwixt.strategy.DefaultPluralStemmer; |
|
52 | import org.apache.commons.betwixt.strategy.NameMapper; |
|
53 | import org.apache.commons.betwixt.strategy.PluralStemmer; |
|
54 | import org.apache.commons.betwixt.strategy.TypeBindingStrategy; |
|
55 | import org.apache.commons.logging.Log; |
|
56 | import org.apache.commons.logging.LogFactory; |
|
57 | import org.xml.sax.InputSource; |
|
58 | import org.xml.sax.SAXException; |
|
59 | ||
60 | /** |
|
61 | * <p><code>XMLIntrospector</code> an introspector of beans to create a |
|
62 | * XMLBeanInfo instance.</p> |
|
63 | * |
|
64 | * <p>By default, <code>XMLBeanInfo</code> caching is switched on. |
|
65 | * This means that the first time that a request is made for a <code>XMLBeanInfo</code> |
|
66 | * for a particular class, the <code>XMLBeanInfo</code> is cached. |
|
67 | * Later requests for the same class will return the cached value.</p> |
|
68 | * |
|
69 | * <p>Note :</p> |
|
70 | * <p>This class makes use of the <code>java.bean.Introspector</code> |
|
71 | * class, which contains a BeanInfoSearchPath. To make sure betwixt can |
|
72 | * do his work correctly, this searchpath is completely ignored during |
|
73 | * processing. The original values will be restored after processing finished |
|
74 | * </p> |
|
75 | * |
|
76 | * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> |
|
77 | * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a> |
|
78 | */ |
|
79 | public class XMLIntrospector { |
|
80 | /** |
|
81 | * Log used for logging (Doh!) |
|
82 | * @deprecated 0.6 use the {@link #getLog()} property instead |
|
83 | 2450 | */ |
84 | 2530 | protected Log log = LogFactory.getLog( XMLIntrospector.class ); |
85 | ||
86 | /** Maps classes to <code>XMLBeanInfo</code>'s */ |
|
87 | private XMLBeanInfoRegistry registry; |
|
88 | ||
89 | /** Digester used to parse the XML descriptor files */ |
|
90 | private XMLBeanInfoDigester digester; |
|
91 | ||
92 | /** Digester used to parse the multi-mapping XML descriptor files */ |
|
93 | private MultiMappingBeanInfoDigester multiMappingdigester; |
|
94 | ||
95 | /** Configuration to be used for introspection*/ |
|
96 | private IntrospectionConfiguration configuration; |
|
97 | ||
98 | /** |
|
99 | * Resolves polymorphic references. |
|
100 | * Though this is used only at bind time, |
|
101 | * it is typically tightly couple to the xml registry. |
|
102 | * It is therefore convenient to keep both references together. |
|
103 | */ |
|
104 | private PolymorphicReferenceResolver polymorphicReferenceResolver; |
|
105 | ||
106 | /** Base constructor */ |
|
107 | 2450 | public XMLIntrospector() { |
108 | 4568 | this(new IntrospectionConfiguration()); |
109 | 2118 | } |
110 | ||
111 | /** |
|
112 | * Construct allows a custom configuration to be set on construction. |
|
113 | * This allows <code>IntrospectionConfiguration</code> subclasses |
|
114 | * to be easily used. |
|
115 | * @param configuration IntrospectionConfiguration, not null |
|
116 | 2450 | */ |
117 | 4568 | public XMLIntrospector(IntrospectionConfiguration configuration) { |
118 | 4568 | setConfiguration(configuration); |
119 | 4568 | DefaultXMLBeanInfoRegistry defaultRegistry |
120 | 3156 | = new DefaultXMLBeanInfoRegistry(); |
121 | 4568 | setRegistry(defaultRegistry); |
122 | 4568 | setPolymorphicReferenceResolver(defaultRegistry); |
123 | 2118 | } |
124 | ||
125 | ||
126 | // Properties |
|
127 | //------------------------------------------------------------------------- |
|
128 | ||
129 | /** |
|
130 | * <p>Gets the current logging implementation. </p> |
|
131 | * @return the Log implementation which this class logs to |
|
132 | */ |
|
133 | 53879 | public Log getLog() { |
134 | 46746 | return getConfiguration().getIntrospectionLog(); |
135 | } |
|
136 | ||
137 | /** |
|
138 | * <p>Sets the current logging implementation.</p> |
|
139 | * @param log the Log implementation to use for logging |
|
140 | */ |
|
141 | 0 | public void setLog(Log log) { |
142 | 0 | getConfiguration().setIntrospectionLog(log); |
143 | 0 | } |
144 | ||
145 | /** |
|
146 | * <p>Gets the current registry implementation. |
|
147 | * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class |
|
148 | * before introspecting. |
|
149 | * After standard introspection is complete, the instance will be passed to the registry.</p> |
|
150 | * |
|
151 | * <p>This allows finely grained control over the caching strategy. |
|
152 | * It also allows the standard introspection mechanism |
|
153 | * to be overridden on a per class basis.</p> |
|
154 | * |
|
155 | * @return the XMLBeanInfoRegistry currently used |
|
156 | */ |
|
157 | 1253 | public XMLBeanInfoRegistry getRegistry() { |
158 | 1122 | return registry; |
159 | } |
|
160 | ||
161 | /** |
|
162 | * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation. |
|
163 | * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class |
|
164 | * before introspecting. |
|
165 | * After standard introspection is complete, the instance will be passed to the registry.</p> |
|
166 | * |
|
167 | * <p>This allows finely grained control over the caching strategy. |
|
168 | * It also allows the standard introspection mechanism |
|
169 | * to be overridden on a per class basis.</p> |
|
170 | * |
|
171 | * <p><strong>Note</strong> when using polymophic mapping with a custom |
|
172 | * registry, a call to |
|
173 | * {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)} |
|
174 | * may be necessary. |
|
175 | * </p> |
|
176 | * @param registry the XMLBeanInfoRegistry to use |
|
177 | */ |
|
178 | 2478 | public void setRegistry(XMLBeanInfoRegistry registry) { |
179 | 4620 | this.registry = registry; |
180 | 2142 | } |
181 | ||
182 | /** |
|
183 | * Gets the configuration to be used for introspection. |
|
184 | * The various introspection-time strategies |
|
185 | * and configuration variables have been consolidated as properties |
|
186 | * of this bean. |
|
187 | * This allows the configuration to be more easily shared. |
|
188 | * @return IntrospectionConfiguration, not null |
|
189 | */ |
|
190 | 133581 | public IntrospectionConfiguration getConfiguration() { |
191 | 115536 | return configuration; |
192 | } |
|
193 | ||
194 | /** |
|
195 | * Sets the configuration to be used for introspection. |
|
196 | * The various introspection-time strategies |
|
197 | * and configuration variables have been consolidated as properties |
|
198 | * of this bean. |
|
199 | * This allows the configuration to be more easily shared. |
|
200 | * @param configuration IntrospectionConfiguration, not null |
|
201 | */ |
|
202 | 2555 | public void setConfiguration(IntrospectionConfiguration configuration) { |
203 | 4763 | this.configuration = configuration; |
204 | 2208 | } |
205 | ||
206 | ||
207 | /** |
|
208 | * Gets the <code>ClassNormalizer</code> strategy. |
|
209 | * This is used to determine the Class to be introspected |
|
210 | * (the normalized Class). |
|
211 | * |
|
212 | * @return the <code>ClassNormalizer</code> used to determine the Class to be introspected |
|
213 | * for a given Object. |
|
214 | * @deprecated 0.6 use getConfiguration().getClassNormalizer |
|
215 | * @since 0.5 |
|
216 | */ |
|
217 | 5061 | public ClassNormalizer getClassNormalizer() { |
218 | 4356 | return getConfiguration().getClassNormalizer(); |
219 | } |
|
220 | ||
221 | /** |
|
222 | * Sets the <code>ClassNormalizer</code> strategy. |
|
223 | * This is used to determine the Class to be introspected |
|
224 | * (the normalized Class). |
|
225 | * |
|
226 | * @param classNormalizer the <code>ClassNormalizer</code> to be used to determine |
|
227 | * the Class to be introspected for a given Object. |
|
228 | * @deprecated 0.6 use getConfiguration().setClassNormalizer |
|
229 | * @since 0.5 |
|
230 | * |
|
231 | */ |
|
232 | 0 | public void setClassNormalizer(ClassNormalizer classNormalizer) { |
233 | 0 | getConfiguration().setClassNormalizer(classNormalizer); |
234 | 0 | } |
235 | ||
236 | ||
237 | ||
238 | /** |
|
239 | * <p>Gets the resolver for polymorphic references.</p> |
|
240 | * <p> |
|
241 | * Though this is used only at bind time, |
|
242 | * it is typically tightly couple to the xml registry. |
|
243 | * It is therefore convenient to keep both references together. |
|
244 | * </p> |
|
245 | * <p><strong>Note:</strong> though the implementation is |
|
246 | * set initially to the default registry, |
|
247 | * this reference is not updated when {@link #setRegistry(XMLBeanInfoRegistry)} |
|
248 | * is called. Therefore, a call to {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)} |
|
249 | * with the instance may be necessary. |
|
250 | * </p> |
|
251 | * @since 0.7 |
|
252 | * @return <code>PolymorphicReferenceResolver</code>, not null |
|
253 | */ |
|
254 | 259 | public PolymorphicReferenceResolver getPolymorphicReferenceResolver() { |
255 | 252 | return polymorphicReferenceResolver; |
256 | } |
|
257 | ||
258 | /** |
|
259 | * <p>Sets the resolver for polymorphic references.</p> |
|
260 | * <p> |
|
261 | * Though this is used only at bind time, |
|
262 | * it is typically tightly couple to the xml registry. |
|
263 | * It is therefore convenient to keep both references together. |
|
264 | * </p> |
|
265 | * <p><strong>Note:</strong> though the implementation is |
|
266 | * set initially to the default registry, |
|
267 | * this reference is not updated when {@link #setRegistry(XMLBeanInfoRegistry)} |
|
268 | * is called. Therefore, a call to {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)} |
|
269 | * with the instance may be necessary. |
|
270 | * </p> |
|
271 | * @since 0.7 |
|
272 | * @param polymorphicReferenceResolver The polymorphicReferenceResolver to set. |
|
273 | */ |
|
274 | public void setPolymorphicReferenceResolver( |
|
275 | 2450 | PolymorphicReferenceResolver polymorphicReferenceResolver) { |
276 | 4568 | this.polymorphicReferenceResolver = polymorphicReferenceResolver; |
277 | 2118 | } |
278 | ||
279 | /** |
|
280 | * Is <code>XMLBeanInfo</code> caching enabled? |
|
281 | * |
|
282 | * @deprecated 0.5 replaced by XMlBeanInfoRegistry |
|
283 | * @return true if caching is enabled |
|
284 | */ |
|
285 | 0 | public boolean isCachingEnabled() { |
286 | 0 | return true; |
287 | } |
|
288 | ||
289 | /** |
|
290 | * Set whether <code>XMLBeanInfo</code> caching should be enabled. |
|
291 | * |
|
292 | * @deprecated 0.5 replaced by XMlBeanInfoRegistry |
|
293 | * @param cachingEnabled ignored |
|
294 | */ |
|
295 | public void setCachingEnabled(boolean cachingEnabled) { |
|
296 | 0 | // |
297 | 0 | } |
298 | ||
299 | ||
300 | /** |
|
301 | * Should attributes (or elements) be used for primitive types. |
|
302 | * @return true if primitive types will be mapped to attributes in the introspection |
|
303 | * @deprecated 0.6 use getConfiguration().isAttributesForPrimitives |
|
304 | */ |
|
305 | 0 | public boolean isAttributesForPrimitives() { |
306 | 0 | return getConfiguration().isAttributesForPrimitives(); |
307 | } |
|
308 | ||
309 | /** |
|
310 | * Set whether attributes (or elements) should be used for primitive types. |
|
311 | * @param attributesForPrimitives pass trus to map primitives to attributes, |
|
312 | * pass false to map primitives to elements |
|
313 | * @deprecated 0.6 use getConfiguration().setAttributesForPrimitives |
|
314 | */ |
|
315 | 0 | public void setAttributesForPrimitives(boolean attributesForPrimitives) { |
316 | 0 | getConfiguration().setAttributesForPrimitives(attributesForPrimitives); |
317 | 0 | } |
318 | ||
319 | /** |
|
320 | * Should collections be wrapped in an extra element? |
|
321 | * |
|
322 | * @return whether we should we wrap collections in an extra element? |
|
323 | * @deprecated 0.6 use getConfiguration().isWrapCollectionsInElement |
|
324 | */ |
|
325 | 0 | public boolean isWrapCollectionsInElement() { |
326 | 0 | return getConfiguration().isWrapCollectionsInElement(); |
327 | } |
|
328 | ||
329 | /** |
|
330 | * Sets whether we should we wrap collections in an extra element. |
|
331 | * |
|
332 | * @param wrapCollectionsInElement pass true if collections should be wrapped in a |
|
333 | * parent element |
|
334 | * @deprecated 0.6 use getConfiguration().setWrapCollectionsInElement |
|
335 | */ |
|
336 | 0 | public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) { |
337 | 0 | getConfiguration().setWrapCollectionsInElement(wrapCollectionsInElement); |
338 | 0 | } |
339 | ||
340 | /** |
|
341 | * Get singular and plural matching strategy. |
|
342 | * |
|
343 | * @return the strategy used to detect matching singular and plural properties |
|
344 | * @deprecated 0.6 use getConfiguration().getPluralStemmer |
|
345 | */ |
|
346 | 1386 | public PluralStemmer getPluralStemmer() { |
347 | 1224 | return getConfiguration().getPluralStemmer(); |
348 | } |
|
349 | ||
350 | /** |
|
351 | * Sets the strategy used to detect matching singular and plural properties |
|
352 | * |
|
353 | * @param pluralStemmer the PluralStemmer used to match singular and plural |
|
354 | * @deprecated 0.6 use getConfiguration().setPluralStemmer |
|
355 | */ |
|
356 | 0 | public void setPluralStemmer(PluralStemmer pluralStemmer) { |
357 | 0 | getConfiguration().setPluralStemmer(pluralStemmer); |
358 | 0 | } |
359 | ||
360 | /** |
|
361 | * Gets the name mapper strategy. |
|
362 | * |
|
363 | * @return the strategy used to convert bean type names into element names |
|
364 | * @deprecated 0.5 getNameMapper is split up in |
|
365 | * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()} |
|
366 | */ |
|
367 | 0 | public NameMapper getNameMapper() { |
368 | 0 | return getElementNameMapper(); |
369 | } |
|
370 | ||
371 | /** |
|
372 | * Sets the strategy used to convert bean type names into element names |
|
373 | * @param nameMapper the NameMapper strategy to be used |
|
374 | * @deprecated 0.5 setNameMapper is split up in |
|
375 | * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)} |
|
376 | */ |
|
377 | 0 | public void setNameMapper(NameMapper nameMapper) { |
378 | 0 | setElementNameMapper(nameMapper); |
379 | 0 | } |
380 | ||
381 | ||
382 | /** |
|
383 | * Gets the name mapping strategy used to convert bean names into elements. |
|
384 | * |
|
385 | * @return the strategy used to convert bean type names into element |
|
386 | * names. If no element mapper is currently defined then a default one is created. |
|
387 | * @deprecated 0.6 use getConfiguration().getElementNameMapper |
|
388 | */ |
|
389 | 2758 | public NameMapper getElementNameMapper() { |
390 | 2388 | return getConfiguration().getElementNameMapper(); |
391 | } |
|
392 | ||
393 | /** |
|
394 | * Sets the strategy used to convert bean type names into element names |
|
395 | * @param nameMapper the NameMapper to use for the conversion |
|
396 | * @deprecated 0.6 use getConfiguration().setElementNameMapper |
|
397 | */ |
|
398 | 0 | public void setElementNameMapper(NameMapper nameMapper) { |
399 | 0 | getConfiguration().setElementNameMapper( nameMapper ); |
400 | 0 | } |
401 | ||
402 | ||
403 | /** |
|
404 | * Gets the name mapping strategy used to convert bean names into attributes. |
|
405 | * |
|
406 | * @return the strategy used to convert bean type names into attribute |
|
407 | * names. If no attributeNamemapper is known, it will default to the ElementNameMapper |
|
408 | * @deprecated 0.6 getConfiguration().getAttributeNameMapper |
|
409 | */ |
|
410 | 0 | public NameMapper getAttributeNameMapper() { |
411 | 0 | return getConfiguration().getAttributeNameMapper(); |
412 | } |
|
413 | ||
414 | ||
415 | /** |
|
416 | * Sets the strategy used to convert bean type names into attribute names |
|
417 | * @param nameMapper the NameMapper to use for the convertion |
|
418 | * @deprecated 0.6 use getConfiguration().setAttributeNameMapper |
|
419 | */ |
|
420 | 0 | public void setAttributeNameMapper(NameMapper nameMapper) { |
421 | 0 | getConfiguration().setAttributeNameMapper( nameMapper ); |
422 | 0 | } |
423 | ||
424 | /** |
|
425 | * Should the original <code>java.reflect.Introspector</code> bean info search path be used? |
|
426 | * By default it will be false. |
|
427 | * |
|
428 | * @return boolean if the beanInfoSearchPath should be used. |
|
429 | * @deprecated 0.6 use getConfiguration().useBeanInfoSearchPath |
|
430 | */ |
|
431 | 0 | public boolean useBeanInfoSearchPath() { |
432 | 0 | return getConfiguration().useBeanInfoSearchPath(); |
433 | } |
|
434 | ||
435 | /** |
|
436 | * Specifies if you want to use the beanInfoSearchPath |
|
437 | * @see java.beans.Introspector for more details |
|
438 | * @param useBeanInfoSearchPath |
|
439 | * @deprecated 0.6 use getConfiguration().setUseBeanInfoSearchPath |
|
440 | */ |
|
441 | 0 | public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) { |
442 | 0 | getConfiguration().setUseBeanInfoSearchPath( useBeanInfoSearchPath ); |
443 | 0 | } |
444 | ||
445 | // Methods |
|
446 | //------------------------------------------------------------------------- |
|
447 | ||
448 | /** |
|
449 | * Flush existing cached <code>XMLBeanInfo</code>'s. |
|
450 | * |
|
451 | * @deprecated 0.5 use flushable registry instead |
|
452 | 0 | */ |
453 | 0 | public void flushCache() {} |
454 | ||
455 | ||
456 | /** Create a standard <code>XMLBeanInfo</code> by introspection |
|
457 | * The actual introspection depends only on the <code>BeanInfo</code> |
|
458 | * associated with the bean. |
|
459 | * |
|
460 | * @param bean introspect this bean |
|
461 | * @return XMLBeanInfo describing bean-xml mapping |
|
462 | * @throws IntrospectionException when the bean introspection fails |
|
463 | */ |
|
464 | 5082 | public XMLBeanInfo introspect(Object bean) throws IntrospectionException { |
465 | 4374 | if (getLog().isDebugEnabled()) { |
466 | 0 | getLog().debug( "Introspecting..." ); |
467 | 0 | getLog().debug(bean); |
468 | } |
|
469 | 5082 | |
470 | 4374 | if ( bean instanceof DynaBean ) { |
471 | 21 | // allow DynaBean implementations to be overridden by .betwixt files |
472 | 39 | XMLBeanInfo xmlBeanInfo = findByXMLDescriptor( bean.getClass() ); |
473 | 25 | if (xmlBeanInfo != null) { |
474 | 6 | return xmlBeanInfo; |
475 | } |
|
476 | 14 | // this is DynaBean use the DynaClass for introspection |
477 | 12 | return introspect( ((DynaBean) bean).getDynaClass() ); |
478 | ||
479 | } else { |
|
480 | 5061 | // normal bean so normal introspection |
481 | 9417 | Class normalClass = getClassNormalizer().getNormalizedClass( bean ); |
482 | 4356 | return introspect( normalClass ); |
483 | } |
|
484 | } |
|
485 | ||
486 | /** |
|
487 | * Creates XMLBeanInfo by reading the DynaProperties of a DynaBean. |
|
488 | * Customizing DynaBeans using betwixt is not supported. |
|
489 | * |
|
490 | * @param dynaClass the DynaBean to introspect |
|
491 | * |
|
492 | * @return XMLBeanInfo for the DynaClass |
|
493 | */ |
|
494 | public XMLBeanInfo introspect(DynaClass dynaClass) { |
|
495 | ||
496 | // for now this method does not do much, since XMLBeanInfoRegistry cannot |
|
497 | // use a DynaClass as a key |
|
498 | // TODO: add caching for DynaClass XMLBeanInfo |
|
499 | // need to work out if this is possible |
|
500 | ||
501 | 21 | // this line allows subclasses to change creation strategy |
502 | 18 | XMLBeanInfo xmlInfo = createXMLBeanInfo( dynaClass ); |
503 | ||
504 | 21 | // populate the created info with |
505 | 39 | DynaClassBeanType beanClass = new DynaClassBeanType( dynaClass ); |
506 | 18 | populate( xmlInfo, beanClass ); |
507 | 21 | |
508 | 18 | return xmlInfo; |
509 | } |
|
510 | ||
511 | ||
512 | /** |
|
513 | * <p>Introspects the given <code>Class</code> using the dot betwixt |
|
514 | * document in the given <code>InputSource</code>. |
|
515 | * </p> |
|
516 | * <p> |
|
517 | * <strong>Note:</strong> that the given mapping will <em>not</em> |
|
518 | * be registered by this method. Use {@link #register(Class, InputSource)} |
|
519 | * instead. |
|
520 | * </p> |
|
521 | * @since 0.7 |
|
522 | * @param aClass <code>Class</code>, not null |
|
523 | * @param source <code>InputSource</code>, not null |
|
524 | * @return <code>XMLBeanInfo</code> describing the mapping. |
|
525 | * @throws SAXException when the input source cannot be parsed |
|
526 | * @throws IOException |
|
527 | */ |
|
528 | public synchronized XMLBeanInfo introspect(Class aClass, InputSource source) throws IOException, SAXException { |
|
529 | 63 | // need to synchronize since we only use one instance and SAX is essentially one thread only |
530 | 117 | configureDigester(aClass); |
531 | 117 | XMLBeanInfo result = (XMLBeanInfo) digester.parse(source); |
532 | 54 | return result; |
533 | } |
|
534 | ||
535 | ||
536 | /** Create a standard <code>XMLBeanInfo</code> by introspection. |
|
537 | * The actual introspection depends only on the <code>BeanInfo</code> |
|
538 | * associated with the bean. |
|
539 | * |
|
540 | * @param aClass introspect this class |
|
541 | * @return XMLBeanInfo describing bean-xml mapping |
|
542 | * @throws IntrospectionException when the bean introspection fails |
|
543 | */ |
|
544 | public XMLBeanInfo introspect(Class aClass) throws IntrospectionException { |
|
545 | 11627 | // we first reset the beaninfo searchpath. |
546 | 21713 | String[] searchPath = null; |
547 | 10086 | if ( !getConfiguration().useBeanInfoSearchPath() ) { |
548 | 11627 | try { |
549 | 21713 | searchPath = Introspector.getBeanInfoSearchPath(); |
550 | 10086 | Introspector.setBeanInfoSearchPath(new String[] { }); |
551 | 0 | } catch (SecurityException e) { |
552 | 0 | // this call may fail in some environments |
553 | 0 | getLog().warn("Security manager does not allow bean info search path to be set"); |
554 | 0 | getLog().debug("Security exception whilst setting bean info search page", e); |
555 | 6724 | } |
556 | } |
|
557 | 11627 | |
558 | 10086 | XMLBeanInfo xmlInfo = registry.get( aClass ); |
559 | 11627 | |
560 | 10086 | if ( xmlInfo == null ) { |
561 | 3759 | // lets see if we can find an XML descriptor first |
562 | 3246 | if ( getLog().isDebugEnabled() ) { |
563 | 0 | getLog().debug( "Attempting to lookup an XML descriptor for class: " + aClass ); |
564 | } |
|
565 | 3759 | |
566 | 7005 | xmlInfo = findByXMLDescriptor( aClass ); |
567 | 3246 | if ( xmlInfo == null ) { |
568 | 2737 | BeanInfo info; |
569 | 2377 | if(getConfiguration().ignoreAllBeanInfo()) { |
570 | 6 | info = Introspector.getBeanInfo( aClass, Introspector.IGNORE_ALL_BEANINFO ); |
571 | } |
|
572 | 2730 | else { |
573 | 2364 | info = Introspector.getBeanInfo( aClass ); |
574 | 2737 | } |
575 | 2370 | xmlInfo = introspect( info ); |
576 | } |
|
577 | 3759 | |
578 | 7005 | if ( xmlInfo != null ) { |
579 | 3246 | registry.put( aClass, xmlInfo ); |
580 | } |
|
581 | 7868 | } else { |
582 | 6840 | getLog().trace( "Used cached XMLBeanInfo." ); |
583 | } |
|
584 | 11627 | |
585 | 10086 | if ( getLog().isTraceEnabled() ) { |
586 | 0 | getLog().trace( xmlInfo ); |
587 | 11627 | } |
588 | 10086 | if ( !getConfiguration().useBeanInfoSearchPath() && searchPath != null) { |
589 | try |
|
590 | { |
|
591 | 11627 | // we restore the beaninfo searchpath. |
592 | 10086 | Introspector.setBeanInfoSearchPath( searchPath ); |
593 | 0 | } catch (SecurityException e) { |
594 | 0 | // this call may fail in some environments |
595 | 0 | getLog().warn("Security manager does not allow bean info search path to be set"); |
596 | 0 | getLog().debug("Security exception whilst setting bean info search page", e); |
597 | 6724 | } |
598 | } |
|
599 | 11627 | |
600 | 10086 | return xmlInfo; |
601 | } |
|
602 | ||
603 | /** Create a standard <code>XMLBeanInfo</code> by introspection. |
|
604 | * The actual introspection depends only on the <code>BeanInfo</code> |
|
605 | * associated with the bean. |
|
606 | * |
|
607 | * @param beanInfo the BeanInfo the xml-bean mapping is based on |
|
608 | * @return XMLBeanInfo describing bean-xml mapping |
|
609 | * @throws IntrospectionException when the bean introspection fails |
|
610 | */ |
|
611 | 2737 | public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException { |
612 | 5107 | XMLBeanInfo xmlBeanInfo = createXMLBeanInfo( beanInfo ); |
613 | 5107 | populate( xmlBeanInfo, new JavaBeanType( beanInfo ) ); |
614 | 2370 | return xmlBeanInfo; |
615 | } |
|
616 | ||
617 | ||
618 | /** |
|
619 | * <p>Registers the class mappings specified in the multi-class document |
|
620 | * given by the <code>InputSource</code>. |
|
621 | * </p> |
|
622 | * <p> |
|
623 | * <strong>Note:</strong> that this method will override any existing mapping |
|
624 | * for the speficied classes. |
|
625 | * </p> |
|
626 | * @since 0.7 |
|
627 | * @param source <code>InputSource</code>, not null |
|
628 | * @return <code>Class</code> array containing all mapped classes |
|
629 | * @throws IntrospectionException |
|
630 | * @throws SAXException |
|
631 | * @throws IOException |
|
632 | */ |
|
633 | 210 | public synchronized Class[] register(InputSource source) throws IntrospectionException, IOException, SAXException { |
634 | 402 | Map xmlBeanInfoByClass = loadMultiMapping(source); |
635 | 402 | Set keySet = xmlBeanInfoByClass.keySet(); |
636 | 402 | Class mappedClasses[] = new Class[keySet.size()]; |
637 | 1410 | int i=0; |
638 | 1290 | for (Iterator it=keySet.iterator(); it.hasNext(); ) { |
639 | 1506 | Class clazz = (Class) it.next(); |
640 | 1506 | mappedClasses[i++] = clazz; |
641 | 1506 | XMLBeanInfo xmlBeanInfo = (XMLBeanInfo) xmlBeanInfoByClass.get(clazz); |
642 | 1506 | if (xmlBeanInfo != null) { |
643 | 708 | getRegistry().put(clazz, xmlBeanInfo); |
644 | } |
|
645 | 210 | } |
646 | 192 | return mappedClasses; |
647 | } |
|
648 | ||
649 | /** |
|
650 | * Loads the multi-mapping from the given <code>InputSource</code>. |
|
651 | * @param mapping <code>InputSource</code>, not null |
|
652 | * @return <code>Map</code> containing <code>XMLBeanInfo</code>'s |
|
653 | * indexes by the <code>Class</code> they describe |
|
654 | * @throws IOException |
|
655 | * @throws SAXException |
|
656 | */ |
|
657 | private synchronized Map loadMultiMapping(InputSource mapping) throws IOException, SAXException { |
|
658 | // synchronized method so this digester is only used by |
|
659 | 210 | // one thread at once |
660 | 402 | if (multiMappingdigester == null) { |
661 | 402 | multiMappingdigester = new MultiMappingBeanInfoDigester(); |
662 | 192 | multiMappingdigester.setXMLIntrospector(this); |
663 | 210 | } |
664 | 402 | Map multiBeanInfoMap = (Map) multiMappingdigester.parse(mapping); |
665 | 192 | return multiBeanInfoMap; |
666 | } |
|
667 | ||
668 | /** |
|
669 | * <p>Registers the class mapping specified in the standard dot-betwixt file. |
|
670 | * Subsequent introspections will use this registered mapping for the class. |
|
671 | * </p> |
|
672 | * <p> |
|
673 | * <strong>Note:</strong> that this method will override any existing mapping |
|
674 | * for this class. |
|
675 | * </p> |
|
676 | * @since 0.7 |
|
677 | * @param aClass <code>Class</code>, not null |
|
678 | * @param source <code>InputSource</code>, not null |
|
679 | * @throws SAXException when the source cannot be parsed |
|
680 | * @throws IOException |
|
681 | */ |
|
682 | 21 | public void register(Class aClass, InputSource source) throws IOException, SAXException { |
683 | 39 | XMLBeanInfo xmlBeanInfo = introspect(aClass, source); |
684 | 39 | getRegistry().put(aClass, xmlBeanInfo); |
685 | 18 | } |
686 | ||
687 | /** |
|
688 | * Populates the given <code>XMLBeanInfo</code> based on the given type of bean. |
|
689 | * |
|
690 | * @param xmlBeanInfo populate this, not null |
|
691 | * @param bean the type definition for the bean, not null |
|
692 | */ |
|
693 | 2758 | private void populate(XMLBeanInfo xmlBeanInfo, BeanType bean) { |
694 | 2388 | String name = bean.getBeanName(); |
695 | 2758 | |
696 | 7904 | ElementDescriptor elementDescriptor = new ElementDescriptor(); |
697 | 5942 | elementDescriptor.setLocalName( |
698 | 3554 | getElementNameMapper().mapTypeToElementName( name ) ); |
699 | 2388 | elementDescriptor.setPropertyType( bean.getElementType() ); |
700 | 2758 | |
701 | 2388 | if (getLog().isTraceEnabled()) { |
702 | 0 | getLog().trace("Populating:" + bean); |
703 | } |
|
704 | ||
705 | 2758 | // add default string value for primitive types |
706 | 2619 | if ( bean.isPrimitiveType() ) { |
707 | 429 | getLog().trace("Bean is primitive"); |
708 | 198 | elementDescriptor.setTextExpression( StringExpression.getInstance() ); |
709 | ||
710 | } else { |
|
711 | 2527 | |
712 | 2190 | getLog().trace("Bean is standard type"); |
713 | 2527 | |
714 | 2190 | boolean isLoopType = bean.isLoopType(); |
715 | 2527 | |
716 | 4717 | List elements = new ArrayList(); |
717 | 4717 | List attributes = new ArrayList(); |
718 | 2190 | List contents = new ArrayList(); |
719 | ||
720 | 2527 | // add bean properties for all collection which are not basic |
721 | 2190 | if ( !( isLoopType && isBasicCollection( bean.getClass() ) ) ) |
722 | 2527 | { |
723 | 2190 | addProperties( bean.getProperties(), elements, attributes, contents ); |
724 | } |
|
725 | ||
726 | 2527 | // add iterator for collections |
727 | 2337 | if ( isLoopType ) { |
728 | 291 | getLog().trace("Bean is loop"); |
729 | 291 | ElementDescriptor loopDescriptor = new ElementDescriptor(); |
730 | 438 | loopDescriptor.setCollective(true); |
731 | 291 | loopDescriptor.setHollow(true); |
732 | 144 | loopDescriptor.setSingularPropertyType(Object.class); |
733 | 339 | loopDescriptor.setContextExpression( |
734 | 62 | new IteratorExpression( EmptyExpression.getInstance() ) |
735 | ); |
|
736 | 291 | loopDescriptor.setUpdater(CollectionUpdater.getInstance()); |
737 | 144 | if ( bean.isMapType() ) { |
738 | 12 | loopDescriptor.setQualifiedName( "entry" ); |
739 | 2527 | } |
740 | 2671 | elements.add( loopDescriptor ); |
741 | 2100 | } |
742 | 2100 | |
743 | 4290 | int size = elements.size(); |
744 | 2190 | if ( size > 0 ) { |
745 | 4345 | ElementDescriptor[] descriptors = new ElementDescriptor[size]; |
746 | 4345 | elements.toArray( descriptors ); |
747 | 2553 | elementDescriptor.setElementDescriptors( descriptors ); |
748 | 735 | } |
749 | 2925 | size = attributes.size(); |
750 | 2190 | if ( size > 0 ) { |
751 | 3157 | AttributeDescriptor[] descriptors = new AttributeDescriptor[size]; |
752 | 3157 | attributes.toArray( descriptors ); |
753 | 630 | elementDescriptor.setAttributeDescriptors( descriptors ); |
754 | 0 | } |
755 | 2190 | size = contents.size(); |
756 | 2190 | if ( size > 0 ) { |
757 | 0 | if ( size > 0 ) { |
758 | 0 | Descriptor[] descriptors = new Descriptor[size]; |
759 | 0 | contents.toArray( descriptors ); |
760 | 0 | elementDescriptor.setContentDescriptors( descriptors ); |
761 | 2758 | } |
762 | } |
|
763 | } |
|
764 | 2758 | |
765 | 2388 | xmlBeanInfo.setElementDescriptor( elementDescriptor ); |
766 | 2758 | |
767 | 0 | // default any addProperty() methods |
768 | 2388 | defaultAddMethods( elementDescriptor, bean.getElementType() ); |
769 | ||
770 | 5146 | if (getLog().isTraceEnabled()) { |
771 | 0 | getLog().trace("Populated descriptor:"); |
772 | 0 | getLog().trace(elementDescriptor); |
773 | } |
|
774 | 2388 | } |
775 | ||
776 | /** |
|
777 | * <p>Is the given type a basic collection? |
|
778 | * </p><p> |
|
779 | * This is used to determine whether a collective type |
|
780 | * should be introspected as a bean (in addition to a collection). |
|
781 | * </p> |
|
782 | * @param type <code>Class</code>, not null |
|
783 | 147 | * @return |
784 | */ |
|
785 | private boolean isBasicCollection( Class type ) |
|
786 | { |
|
787 | 144 | return type.getName().startsWith( "java.util" ); |
788 | } |
|
789 | ||
790 | /** |
|
791 | * Creates XMLBeanInfo for the given DynaClass. |
|
792 | * |
|
793 | * @param dynaClass the class describing a DynaBean |
|
794 | * |
|
795 | * @return XMLBeanInfo that describes the properties of the given |
|
796 | 21 | * DynaClass |
797 | 21 | */ |
798 | protected XMLBeanInfo createXMLBeanInfo(DynaClass dynaClass) { |
|
799 | // XXX is the chosen class right? |
|
800 | 18 | XMLBeanInfo beanInfo = new XMLBeanInfo(dynaClass.getClass()); |
801 | 18 | return beanInfo; |
802 | } |
|
803 | ||
804 | ||
805 | ||
806 | ||
807 | /** |
|
808 | * Create a XML descriptor from a bean one. |
|
809 | * Go through and work out whether it's a loop property, a primitive or a standard. |
|
810 | * The class property is ignored. |
|
811 | * |
|
812 | * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property |
|
813 | * @param useAttributesForPrimitives write primitives as attributes (rather than elements) |
|
814 | * @return a correctly configured <code>NodeDescriptor</code> for the property |
|
815 | * @throws IntrospectionException when bean introspection fails |
|
816 | * @deprecated 0.5 use {@link #createXMLDescriptor}. |
|
817 | */ |
|
818 | 0 | public Descriptor createDescriptor( |
819 | PropertyDescriptor propertyDescriptor, |
|
820 | boolean useAttributesForPrimitives |
|
821 | ) throws IntrospectionException { |
|
822 | 0 | return createXMLDescriptor( new BeanProperty( propertyDescriptor ) ); |
823 | } |
|
824 | ||
825 | /** |
|
826 | * Create a XML descriptor from a bean one. |
|
827 | * Go through and work out whether it's a loop property, a primitive or a standard. |
|
828 | * The class property is ignored. |
|
829 | * |
|
830 | * @param beanProperty the BeanProperty specifying the property |
|
831 | 7378 | * @return a correctly configured <code>NodeDescriptor</code> for the property |
832 | * @since 0.5 |
|
833 | */ |
|
834 | public Descriptor createXMLDescriptor( BeanProperty beanProperty ) { |
|
835 | 6324 | return beanProperty.createXMLDescriptor( configuration ); |
836 | } |
|
837 | ||
838 | ||
839 | /** |
|
840 | * Add any addPropety(PropertyType) methods as Updaters |
|
841 | * which are often used for 1-N relationships in beans. |
|
842 | * This method does not preserve null property names. |
|
843 | * <br> |
|
844 | * The tricky part here is finding which ElementDescriptor corresponds |
|
845 | * to the method. e.g. a property 'items' might have an Element descriptor |
|
846 | * which the method addItem() should match to. |
|
847 | * <br> |
|
848 | * So the algorithm we'll use |
|
849 | * by default is to take the decapitalized name of the property being added |
|
850 | * and find the first ElementDescriptor that matches the property starting with |
|
851 | * the string. This should work for most use cases. |
|
852 | * e.g. addChild() would match the children property. |
|
853 | * <br> |
|
854 | * TODO this probably needs refactoring. It probably belongs in the bean wrapper |
|
855 | * (so that it'll work properly with dyna-beans) and so that the operations can |
|
856 | * be optimized by caching. Multiple hash maps are created and getMethods is |
|
857 | * called multiple times. This is relatively expensive and so it'd be better |
|
858 | * to push into a proper class and cache. |
|
859 | * <br> |
|
860 | * |
|
861 | * @param rootDescriptor add defaults to this descriptor |
|
862 | * @param beanClass the <code>Class</code> to which descriptor corresponds |
|
863 | 2758 | */ |
864 | 2758 | public void defaultAddMethods( |
865 | ElementDescriptor rootDescriptor, |
|
866 | Class beanClass ) { |
|
867 | 2388 | defaultAddMethods(rootDescriptor, beanClass, false); |
868 | 2388 | } |
869 | ||
870 | /** |
|
871 | * Add any addPropety(PropertyType) methods as Updaters |
|
872 | * which are often used for 1-N relationships in beans. |
|
873 | * <br> |
|
874 | * The tricky part here is finding which ElementDescriptor corresponds |
|
875 | * to the method. e.g. a property 'items' might have an Element descriptor |
|
876 | * which the method addItem() should match to. |
|
877 | * <br> |
|
878 | * So the algorithm we'll use |
|
879 | * by default is to take the decapitalized name of the property being added |
|
880 | * and find the first ElementDescriptor that matches the property starting with |
|
881 | * the string. This should work for most use cases. |
|
882 | * e.g. addChild() would match the children property. |
|
883 | * <br> |
|
884 | * TODO this probably needs refactoring. It probably belongs in the bean wrapper |
|
885 | * (so that it'll work properly with dyna-beans) and so that the operations can |
|
886 | * be optimized by caching. Multiple hash maps are created and getMethods is |
|
887 | * called multiple times. This is relatively expensive and so it'd be better |
|
888 | * to push into a proper class and cache. |
|
889 | * <br> |
|
890 | * |
|
891 | * @param rootDescriptor add defaults to this descriptor |
|
892 | * @param beanClass the <code>Class</code> to which descriptor corresponds |
|
893 | */ |
|
894 | public void defaultAddMethods( ElementDescriptor rootDescriptor, Class beanClass, boolean preservePropertyName ) { |
|
895 | // TODO: this probably does work properly with DynaBeans: need to push |
|
896 | 3227 | // implementation into an class and expose it on BeanType. |
897 | 3220 | |
898 | 3220 | // lets iterate over all methods looking for one of the form |
899 | // add*(PropertyType) |
|
900 | 6010 | if ( beanClass != null ) { |
901 | 63061 | ArrayList singleParameterAdders = new ArrayList(); |
902 | 59841 | ArrayList twinParameterAdders = new ArrayList(); |
903 | 57057 | |
904 | 59841 | Method[] methods = beanClass.getMethods(); |
905 | 52374 | for ( int i = 0, size = methods.length; i < size; i++ ) { |
906 | 49590 | Method method = methods[i]; |
907 | 51039 | String name = method.getName(); |
908 | 51039 | if ( name.startsWith( "add" )) { |
909 | 1449 | // TODO: should we filter out non-void returning methods? |
910 | 0 | // some beans will return something as a helper |
911 | 1314 | Class[] types = method.getParameterTypes(); |
912 | 1314 | if ( types != null) { |
913 | 2763 | if ( getLog().isTraceEnabled() ) { |
914 | 0 | getLog().trace("Searching for match for " + method); |
915 | } |
|
916 | 1260 | |
917 | 2574 | switch (types.length) |
918 | { |
|
919 | 189 | case 1: |
920 | 1305 | singleParameterAdders.add(method); |
921 | 1116 | break; |
922 | case 2: |
|
923 | 198 | twinParameterAdders.add(method); |
924 | 198 | break; |
925 | default: |
|
926 | // ignore |
|
927 | break; |
|
928 | } |
|
929 | 3220 | } |
930 | } |
|
931 | 7700 | } |
932 | 1260 | |
933 | 4044 | Map elementsByPropertyName = makeElementDescriptorMap( rootDescriptor ); |
934 | ||
935 | 4084 | for (Iterator it=singleParameterAdders.iterator();it.hasNext();) { |
936 | 7745 | Method singleParameterAdder = (Method) it.next(); |
937 | 1305 | setIteratorAdder(elementsByPropertyName, singleParameterAdder, preservePropertyName); |
938 | 189 | } |
939 | ||
940 | 3778 | for (Iterator it=twinParameterAdders.iterator();it.hasNext();) { |
941 | 198 | Method twinParameterAdder = (Method) it.next(); |
942 | 198 | setMapAdder(elementsByPropertyName, twinParameterAdder); |
943 | 3220 | } |
944 | ||
945 | 3227 | // need to call this once all the defaults have been added |
946 | // so that all the singular types have been set correctly |
|
947 | 2784 | configureMappingDerivation( rootDescriptor ); |
948 | } |
|
949 | 2790 | } |
950 | ||
951 | /** |
|
952 | * Configures the mapping derivation according to the current |
|
953 | * <code>MappingDerivationStrategy</code> implementation. |
|
954 | 22918 | * This method acts recursively. |
955 | 11459 | * @param rootDescriptor <code>ElementDescriptor</code>, not null |
956 | 11459 | */ |
957 | 11459 | private void configureMappingDerivation(ElementDescriptor descriptor) { |
958 | 32850 | boolean useBindTime = getConfiguration().getMappingDerivationStrategy() |
959 | 11527 | .useBindTimeTypeForMapping(descriptor.getPropertyType(), descriptor.getSingularPropertyType()); |
960 | 9864 | descriptor.setUseBindTimeTypeForMapping(useBindTime); |
961 | 21323 | ElementDescriptor[] childDescriptors = descriptor.getElementDescriptors(); |
962 | 16944 | for (int i=0, size=childDescriptors.length; i<size; i++) { |
963 | 7080 | configureMappingDerivation(childDescriptors[i]); |
964 | } |
|
965 | 9864 | } |
966 | ||
967 | /** |
|
968 | * Sets the adder method where the corresponding property is an iterator |
|
969 | * @param rootDescriptor |
|
970 | * @param singleParameterAdder |
|
971 | */ |
|
972 | private void setIteratorAdder( |
|
973 | 1260 | Map elementsByPropertyName, |
974 | 1260 | Method singleParameterAdderMethod, |
975 | 1260 | boolean preserveNullPropertyName) { |
976 | 1260 | |
977 | 1116 | String adderName = singleParameterAdderMethod.getName(); |
978 | 1116 | String propertyName = Introspector.decapitalize(adderName.substring(3)); |
979 | 2313 | ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName); |
980 | 2313 | if (matchingDescriptor != null) { |
981 | 0 | //TODO defensive code: probably should check descriptor type |
982 | ||
983 | 1026 | Class singularType = singleParameterAdderMethod.getParameterTypes()[0]; |
984 | 2223 | if (getLog().isTraceEnabled()) { |
985 | 0 | getLog().trace(adderName + "->" + propertyName); |
986 | 1197 | } |
987 | 1197 | // this may match a standard collection or iteration |
988 | 2223 | getLog().trace("Matching collection or iteration"); |
989 | 1197 | |
990 | 2223 | matchingDescriptor.setUpdater( new MethodUpdater( singleParameterAdderMethod ) ); |
991 | 2734 | matchingDescriptor.setSingularPropertyType( singularType ); |
992 | 1880 | matchingDescriptor.setHollow(!isPrimitiveType(singularType)); |
993 | 1880 | String localName = matchingDescriptor.getLocalName(); |
994 | 1026 | if ( !preserveNullPropertyName && ( localName == null || localName.length() == 0 )) { |
995 | 976 | matchingDescriptor.setLocalName( |
996 | 1441 | getConfiguration().getElementNameMapper() |
997 | 244 | .mapTypeToElementName( propertyName ) ); |
998 | 0 | } |
999 | ||
1000 | 1026 | if ( getLog().isDebugEnabled() ) { |
1001 | 1260 | getLog().debug( "!! " + singleParameterAdderMethod); |
1002 | 0 | getLog().debug( "!! " + singularType); |
1003 | } |
|
1004 | } |
|
1005 | 1116 | } |
1006 | ||
1007 | /** |
|
1008 | * Sets the adder where the corresponding property type is an map |
|
1009 | * @param rootDescriptor |
|
1010 | * @param singleParameterAdder |
|
1011 | 189 | */ |
1012 | 189 | private void setMapAdder( |
1013 | 189 | Map elementsByPropertyName, |
1014 | 189 | Method twinParameterAdderMethod) { |
1015 | 387 | String adderName = twinParameterAdderMethod.getName(); |
1016 | 198 | String propertyName = Introspector.decapitalize(adderName.substring(3)); |
1017 | 198 | ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName); |
1018 | 198 | assignAdder(twinParameterAdderMethod, matchingDescriptor); |
1019 | 198 | } |
1020 | ||
1021 | /** |
|
1022 | * Assigns the given method as an adder method to the given descriptor. |
|
1023 | 210 | * @param twinParameterAdderMethod adder <code>Method</code>, not null |
1024 | 154 | * @param matchingDescriptor <code>ElementDescriptor</code> describing the element |
1025 | */ |
|
1026 | 154 | public void assignAdder(Method twinParameterAdderMethod, ElementDescriptor matchingDescriptor) { |
1027 | 370 | if ( matchingDescriptor != null |
1028 | 198 | && Map.class.isAssignableFrom( matchingDescriptor.getPropertyType() )) { |
1029 | // this may match a map |
|
1030 | 286 | getLog().trace("Matching map"); |
1031 | 132 | ElementDescriptor[] children |
1032 | 44 | = matchingDescriptor.getElementDescriptors(); |
1033 | // see if the descriptor's been set up properly |
|
1034 | 132 | if ( children.length == 0 ) { |
1035 | 0 | getLog().info( |
1036 | 154 | "'entry' descriptor is missing for map. " |
1037 | + "Updaters cannot be set"); |
|
1038 | ||
1039 | 210 | } else { |
1040 | 132 | assignAdder(twinParameterAdderMethod, children); |
1041 | } |
|
1042 | } |
|
1043 | 216 | } |
1044 | ||
1045 | /** |
|
1046 | * Assigns the given method as an adder. |
|
1047 | 154 | * @param twinParameterAdderMethod adder <code>Method</code>, not null |
1048 | 154 | * @param children <code>ElementDescriptor</code> children, not null |
1049 | 154 | */ |
1050 | private void assignAdder(Method twinParameterAdderMethod, ElementDescriptor[] children) { |
|
1051 | 132 | Class[] types = twinParameterAdderMethod.getParameterTypes(); |
1052 | 132 | Class keyType = types[0]; |
1053 | 286 | Class valueType = types[1]; |
1054 | ||
1055 | 154 | // loop through children |
1056 | 154 | // adding updaters for key and value |
1057 | 748 | MapEntryAdder adder = new MapEntryAdder(twinParameterAdderMethod); |
1058 | 308 | for ( |
1059 | 440 | int n=0, |
1060 | 132 | noOfGrandChildren = children.length; |
1061 | 594 | n < noOfGrandChildren; |
1062 | 418 | n++ ) { |
1063 | 418 | if ( "key".equals( children[n].getLocalName() ) ) { |
1064 | 154 | |
1065 | 132 | children[n].setUpdater( adder.getKeyUpdater() ); |
1066 | 286 | children[n].setSingularPropertyType( keyType ); |
1067 | 251 | if (children[n].getPropertyType() == null) { |
1068 | 132 | children[n].setPropertyType( valueType ); |
1069 | 154 | } |
1070 | 132 | if ( isPrimitiveType(keyType) ) { |
1071 | 102 | children[n].setHollow(false); |
1072 | } |
|
1073 | 286 | if ( getLog().isTraceEnabled() ) { |
1074 | 0 | getLog().trace( "Key descriptor: " + children[n]); |
1075 | 154 | } |
1076 | 154 | |
1077 | 286 | } else if ( "value".equals( children[n].getLocalName() ) ) { |
1078 | 154 | |
1079 | 132 | children[n].setUpdater( adder.getValueUpdater() ); |
1080 | 286 | children[n].setSingularPropertyType( valueType ); |
1081 | 223 | if (children[n].getPropertyType() == null) { |
1082 | 132 | children[n].setPropertyType( valueType ); |
1083 | 154 | } |
1084 | 132 | if ( isPrimitiveType( valueType) ) { |
1085 | 78 | children[n].setHollow(false); |
1086 | } |
|
1087 | 153 | if ( isLoopType( valueType )) { |
1088 | 21 | // need to attach a hollow descriptor |
1089 | 21 | // don't know the element name |
1090 | 21 | // so use null name (to match anything) |
1091 | 39 | ElementDescriptor loopDescriptor = new ElementDescriptor(); |
1092 | 39 | loopDescriptor.setHollow(true); |
1093 | 18 | loopDescriptor.setSingularPropertyType( valueType ); |
1094 | 172 | loopDescriptor.setPropertyType( valueType ); |
1095 | 18 | children[n].addElementDescriptor(loopDescriptor); |
1096 | 18 | loopDescriptor.setCollective(true); |
1097 | } |
|
1098 | 132 | if ( getLog().isTraceEnabled() ) { |
1099 | 154 | getLog().trace( "Value descriptor: " + children[n]); |
1100 | } |
|
1101 | } |
|
1102 | } |
|
1103 | 132 | } |
1104 | ||
1105 | /** |
|
1106 | * Gets an ElementDescriptor for the property matching the adder |
|
1107 | * @param adderName |
|
1108 | * @param rootDescriptor |
|
1109 | * @return |
|
1110 | 1449 | */ |
1111 | 1449 | private ElementDescriptor getMatchForAdder( |
1112 | 1386 | String propertyName, |
1113 | 0 | Map elementsByPropertyName) { |
1114 | 1314 | ElementDescriptor matchingDescriptor = null; |
1115 | 1314 | if (propertyName.length() > 0) { |
1116 | 1224 | if ( getLog().isTraceEnabled() ) { |
1117 | 1386 | getLog().trace( "findPluralDescriptor( " + propertyName |
1118 | 1386 | + " ):root property name=" + propertyName ); |
1119 | } |
|
1120 | 1386 | |
1121 | 1224 | PluralStemmer stemmer = getPluralStemmer(); |
1122 | 1224 | matchingDescriptor = stemmer.findPluralDescriptor( propertyName, elementsByPropertyName ); |
1123 | 0 | |
1124 | 1224 | if ( getLog().isTraceEnabled() ) { |
1125 | 0 | getLog().trace( |
1126 | 1449 | "findPluralDescriptor( " + propertyName |
1127 | 0 | + " ):ElementDescriptor=" + matchingDescriptor ); |
1128 | } |
|
1129 | } |
|
1130 | 1314 | return matchingDescriptor; |
1131 | } |
|
1132 | ||
1133 | // Implementation methods |
|
1134 | //------------------------------------------------------------------------- |
|
1135 | ||
1136 | ||
1137 | 3220 | /** |
1138 | 3220 | * Creates a map where the keys are the property names and the values are the ElementDescriptors |
1139 | 3220 | */ |
1140 | 0 | private Map makeElementDescriptorMap( ElementDescriptor rootDescriptor ) { |
1141 | 2784 | Map result = new HashMap(); |
1142 | 6004 | String rootPropertyName = rootDescriptor.getPropertyName(); |
1143 | 6004 | if (rootPropertyName != null) { |
1144 | 0 | result.put(rootPropertyName, rootDescriptor); |
1145 | } |
|
1146 | 2784 | makeElementDescriptorMap( rootDescriptor, result ); |
1147 | 2784 | return result; |
1148 | } |
|
1149 | ||
1150 | /** |
|
1151 | * Creates a map where the keys are the property names and the values are the ElementDescriptors |
|
1152 | * |
|
1153 | * @param rootDescriptor the values of the maps are the children of this |
|
1154 | 11438 | * <code>ElementDescriptor</code> index by their property names |
1155 | 11438 | * @param map the map to which the elements will be added |
1156 | 19656 | */ |
1157 | 8218 | private void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) { |
1158 | 18064 | ElementDescriptor[] children = rootDescriptor.getElementDescriptors(); |
1159 | 18064 | if ( children != null ) { |
1160 | 23516 | for ( int i = 0, size = children.length; i < size; i++ ) { |
1161 | 7062 | ElementDescriptor child = children[i]; |
1162 | 15280 | String propertyName = child.getPropertyName(); |
1163 | 7062 | if ( propertyName != null ) { |
1164 | 5664 | map.put( propertyName, child ); |
1165 | 11438 | } |
1166 | 7062 | makeElementDescriptorMap( child, map ); |
1167 | } |
|
1168 | } |
|
1169 | 9846 | } |
1170 | ||
1171 | /** |
|
1172 | * A Factory method to lazily create a new strategy |
|
1173 | * to detect matching singular and plural properties. |
|
1174 | * |
|
1175 | * @return new defualt PluralStemmer implementation |
|
1176 | 0 | * @deprecated 0.6 this method has been moved into IntrospectionConfiguration. |
1177 | * Those who need to vary this should subclass that class instead |
|
1178 | */ |
|
1179 | protected PluralStemmer createPluralStemmer() { |
|
1180 | 0 | return new DefaultPluralStemmer(); |
1181 | } |
|
1182 | ||
1183 | /** |
|
1184 | * A Factory method to lazily create a strategy |
|
1185 | * used to convert bean type names into element names. |
|
1186 | * |
|
1187 | * @return new default NameMapper implementation |
|
1188 | 0 | * @deprecated 0.6 this method has been moved into IntrospectionConfiguration. |
1189 | * Those who need to vary this should subclass that class instead |
|
1190 | */ |
|
1191 | protected NameMapper createNameMapper() { |
|
1192 | 0 | return new DefaultNameMapper(); |
1193 | } |
|
1194 | ||
1195 | /** |
|
1196 | * Attempt to lookup the XML descriptor for the given class using the |
|
1197 | * classname + ".betwixt" using the same ClassLoader used to load the class |
|
1198 | * or return null if it could not be loaded |
|
1199 | * |
|
1200 | * @param aClass digester .betwixt file for this class |
|
1201 | * @return XMLBeanInfo digested from the .betwixt file if one can be found. |
|
1202 | 3780 | * Otherwise null. |
1203 | 3780 | */ |
1204 | 3780 | protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) { |
1205 | 3780 | // trim the package name |
1206 | 3264 | String name = aClass.getName(); |
1207 | 7044 | int idx = name.lastIndexOf( '.' ); |
1208 | 3264 | if ( idx >= 0 ) { |
1209 | 7044 | name = name.substring( idx + 1 ); |
1210 | 3780 | } |
1211 | 3264 | name += ".betwixt"; |
1212 | 1036 | |
1213 | 4300 | URL url = aClass.getResource( name ); |
1214 | 3264 | if ( url != null ) { |
1215 | try { |
|
1216 | 888 | String urlText = url.toString(); |
1217 | 888 | if ( getLog().isDebugEnabled( )) { |
1218 | 1036 | getLog().debug( "Parsing Betwixt XML descriptor: " + urlText ); |
1219 | 1036 | } |
1220 | 7 | // synchronized method so this digester is only used by |
1221 | 7 | // one thread at once |
1222 | 888 | configureDigester(aClass); |
1223 | 888 | return (XMLBeanInfo) digester.parse( urlText ); |
1224 | 6 | } catch (Exception e) { |
1225 | 2757 | getLog().warn( "Caught exception trying to parse: " + name, e ); |
1226 | 0 | } |
1227 | } |
|
1228 | 2751 | |
1229 | 2382 | if ( getLog().isTraceEnabled() ) { |
1230 | 0 | getLog().trace( "Could not find betwixt file " + name ); |
1231 | } |
|
1232 | 2382 | return null; |
1233 | } |
|
1234 | ||
1235 | /** |
|
1236 | 1099 | * Configures the single <code>Digester</code> instance used by this introspector. |
1237 | 637 | * @param aClass <code>Class</code>, not null |
1238 | 637 | */ |
1239 | private synchronized void configureDigester(Class aClass) { |
|
1240 | 2041 | if ( digester == null ) { |
1241 | 1645 | digester = new XMLBeanInfoDigester(); |
1242 | 546 | digester.setXMLIntrospector( this ); |
1243 | } |
|
1244 | 942 | digester.setBeanClass( aClass ); |
1245 | 942 | } |
1246 | ||
1247 | /** |
|
1248 | * Loop through properties and process each one |
|
1249 | * |
|
1250 | * @param beanInfo the BeanInfo whose properties will be processed |
|
1251 | * @param elements ElementDescriptor list to which elements will be added |
|
1252 | * @param attributes AttributeDescriptor list to which attributes will be added |
|
1253 | * @param contents Descriptor list to which mixed content will be added |
|
1254 | * @throws IntrospectionException if the bean introspection fails |
|
1255 | * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)} |
|
1256 | */ |
|
1257 | protected void addProperties( |
|
1258 | BeanInfo beanInfo, |
|
1259 | List elements, |
|
1260 | 0 | List attributes, |
1261 | 0 | List contents) |
1262 | 0 | throws |
1263 | 0 | IntrospectionException { |
1264 | 0 | PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); |
1265 | 0 | if ( descriptors != null ) { |
1266 | 0 | for ( int i = 0, size = descriptors.length; i < size; i++ ) { |
1267 | 0 | addProperty(beanInfo, descriptors[i], elements, attributes, contents); |
1268 | 0 | } |
1269 | 0 | } |
1270 | 0 | if (getLog().isTraceEnabled()) { |
1271 | 0 | getLog().trace(elements); |
1272 | 0 | getLog().trace(attributes); |
1273 | 0 | getLog().trace(contents); |
1274 | } |
|
1275 | 0 | } |
1276 | /** |
|
1277 | * Loop through properties and process each one |
|
1278 | * |
|
1279 | * @param beanProperties the properties to be processed |
|
1280 | * @param elements ElementDescriptor list to which elements will be added |
|
1281 | * @param attributes AttributeDescriptor list to which attributes will be added |
|
1282 | * @param contents Descriptor list to which mixed content will be added |
|
1283 | * @since 0.5 |
|
1284 | */ |
|
1285 | protected void addProperties( |
|
1286 | 2527 | BeanProperty[] beanProperties, |
1287 | 2527 | List elements, |
1288 | 0 | List attributes, |
1289 | List contents) { |
|
1290 | 10674 | if ( beanProperties != null ) { |
1291 | 8147 | if (getLog().isTraceEnabled()) { |
1292 | 0 | getLog().trace(beanProperties.length + " properties to be added"); |
1293 | } |
|
1294 | 9823 | for ( int i = 0, size = beanProperties.length; i < size; i++ ) { |
1295 | 5106 | addProperty(beanProperties[i], elements, attributes, contents); |
1296 | 0 | } |
1297 | 0 | } |
1298 | 2190 | if (getLog().isTraceEnabled()) { |
1299 | 0 | getLog().trace("After properties have been added (elements, attributes, contents):"); |
1300 | 2527 | getLog().trace(elements); |
1301 | 0 | getLog().trace(attributes); |
1302 | 0 | getLog().trace(contents); |
1303 | } |
|
1304 | 2190 | } |
1305 | ||
1306 | ||
1307 | /** |
|
1308 | * Process a property. |
|
1309 | * Go through and work out whether it's a loop property, a primitive or a standard. |
|
1310 | * The class property is ignored. |
|
1311 | * |
|
1312 | * @param beanInfo the BeanInfo whose property is being processed |
|
1313 | * @param propertyDescriptor the PropertyDescriptor to process |
|
1314 | * @param elements ElementDescriptor list to which elements will be added |
|
1315 | * @param attributes AttributeDescriptor list to which attributes will be added |
|
1316 | * @param contents Descriptor list to which mixed content will be added |
|
1317 | * @throws IntrospectionException if the bean introspection fails |
|
1318 | * @deprecated 0.5 BeanInfo is no longer required. |
|
1319 | * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead. |
|
1320 | */ |
|
1321 | protected void addProperty( |
|
1322 | BeanInfo beanInfo, |
|
1323 | PropertyDescriptor propertyDescriptor, |
|
1324 | List elements, |
|
1325 | 0 | List attributes, |
1326 | 0 | List contents) |
1327 | throws |
|
1328 | IntrospectionException { |
|
1329 | 0 | addProperty( propertyDescriptor, elements, attributes, contents); |
1330 | 0 | } |
1331 | ||
1332 | /** |
|
1333 | * Process a property. |
|
1334 | * Go through and work out whether it's a loop property, a primitive or a standard. |
|
1335 | * The class property is ignored. |
|
1336 | * |
|
1337 | * @param propertyDescriptor the PropertyDescriptor to process |
|
1338 | * @param elements ElementDescriptor list to which elements will be added |
|
1339 | * @param attributes AttributeDescriptor list to which attributes will be added |
|
1340 | * @param contents Descriptor list to which mixed content will be added |
|
1341 | * @throws IntrospectionException if the bean introspection fails |
|
1342 | * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead |
|
1343 | */ |
|
1344 | protected void addProperty( |
|
1345 | PropertyDescriptor propertyDescriptor, |
|
1346 | List elements, |
|
1347 | 0 | List attributes, |
1348 | 0 | List contents) |
1349 | throws |
|
1350 | IntrospectionException { |
|
1351 | 0 | addProperty(new BeanProperty( propertyDescriptor ), elements, attributes, contents); |
1352 | 0 | } |
1353 | ||
1354 | /** |
|
1355 | * Process a property. |
|
1356 | * Go through and work out whether it's a loop property, a primitive or a standard. |
|
1357 | * The class property is ignored. |
|
1358 | * |
|
1359 | * @param beanProperty the bean property to process |
|
1360 | * @param elements ElementDescriptor list to which elements will be added |
|
1361 | * @param attributes AttributeDescriptor list to which attributes will be added |
|
1362 | * @param contents Descriptor list to which mixed content will be added |
|
1363 | * @since 0.5 |
|
1364 | */ |
|
1365 | protected void addProperty( |
|
1366 | 5957 | BeanProperty beanProperty, |
1367 | 5957 | List elements, |
1368 | 49 | List attributes, |
1369 | List contents) { |
|
1370 | 11014 | Descriptor nodeDescriptor = createXMLDescriptor(beanProperty); |
1371 | 9509 | if (nodeDescriptor == null) { |
1372 | 1547 | return; |
1373 | 1505 | } |
1374 | 5064 | if (nodeDescriptor instanceof ElementDescriptor) { |
1375 | 3774 | elements.add(nodeDescriptor); |
1376 | 1290 | } else if (nodeDescriptor instanceof AttributeDescriptor) { |
1377 | 7198 | attributes.add(nodeDescriptor); |
1378 | } else { |
|
1379 | 0 | contents.add(nodeDescriptor); |
1380 | } |
|
1381 | 5064 | } |
1382 | ||
1383 | /** |
|
1384 | * Loop through properties and process each one |
|
1385 | * |
|
1386 | * @param beanInfo the BeanInfo whose properties will be processed |
|
1387 | * @param elements ElementDescriptor list to which elements will be added |
|
1388 | * @param attributes AttributeDescriptor list to which attributes will be added |
|
1389 | * @throws IntrospectionException if the bean introspection fails |
|
1390 | * @deprecated 0.5 this method does not support mixed content. |
|
1391 | * Use {@link #addProperties(BeanInfo, List, List, List)} instead. |
|
1392 | */ |
|
1393 | protected void addProperties( |
|
1394 | BeanInfo beanInfo, |
|
1395 | 0 | List elements, |
1396 | 0 | List attributes) |
1397 | 0 | throws |
1398 | 0 | IntrospectionException { |
1399 | 0 | PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); |
1400 | 0 | if ( descriptors != null ) { |
1401 | 0 | for ( int i = 0, size = descriptors.length; i < size; i++ ) { |
1402 | 0 | addProperty(beanInfo, descriptors[i], elements, attributes); |
1403 | 0 | } |
1404 | } |
|
1405 | 0 | if (getLog().isTraceEnabled()) { |
1406 | 0 | getLog().trace(elements); |
1407 | 0 | getLog().trace(attributes); |
1408 | } |
|
1409 | 0 | } |
1410 | ||
1411 | /** |
|
1412 | * Process a property. |
|
1413 | * Go through and work out whether it's a loop property, a primitive or a standard. |
|
1414 | * The class property is ignored. |
|
1415 | * |
|
1416 | * @param beanInfo the BeanInfo whose property is being processed |
|
1417 | * @param propertyDescriptor the PropertyDescriptor to process |
|
1418 | * @param elements ElementDescriptor list to which elements will be added |
|
1419 | * @param attributes AttributeDescriptor list to which attributes will be added |
|
1420 | * @throws IntrospectionException if the bean introspection fails |
|
1421 | * @deprecated 0.5 this method does not support mixed content. |
|
1422 | * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead. |
|
1423 | */ |
|
1424 | protected void addProperty( |
|
1425 | BeanInfo beanInfo, |
|
1426 | PropertyDescriptor propertyDescriptor, |
|
1427 | 0 | List elements, |
1428 | 0 | List attributes) |
1429 | 0 | throws |
1430 | 0 | IntrospectionException { |
1431 | 0 | NodeDescriptor nodeDescriptor = XMLIntrospectorHelper |
1432 | 0 | .createDescriptor(propertyDescriptor, |
1433 | 0 | isAttributesForPrimitives(), |
1434 | 0 | this); |
1435 | 0 | if (nodeDescriptor == null) { |
1436 | 0 | return; |
1437 | 0 | } |
1438 | 0 | if (nodeDescriptor instanceof ElementDescriptor) { |
1439 | 0 | elements.add(nodeDescriptor); |
1440 | } else { |
|
1441 | 0 | attributes.add(nodeDescriptor); |
1442 | } |
|
1443 | 0 | } |
1444 | ||
1445 | ||
1446 | /** |
|
1447 | * Factory method to create XMLBeanInfo instances |
|
1448 | * |
|
1449 | 2737 | * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created |
1450 | 2737 | * @return XMLBeanInfo describing the bean-xml mapping |
1451 | */ |
|
1452 | protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) { |
|
1453 | 2370 | XMLBeanInfo xmlBeanInfo = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() ); |
1454 | 2370 | return xmlBeanInfo; |
1455 | } |
|
1456 | ||
1457 | /** |
|
1458 | * Is this class a loop? |
|
1459 | * |
|
1460 | 2772 | * @param type the Class to test |
1461 | * @return true if the type is a loop type |
|
1462 | */ |
|
1463 | public boolean isLoopType(Class type) { |
|
1464 | 2376 | return getConfiguration().isLoopType(type); |
1465 | } |
|
1466 | ||
1467 | ||
1468 | /** |
|
1469 | * Is this class a primitive? |
|
1470 | * |
|
1471 | * @param type the Class to test |
|
1472 | * @return true for primitive types |
|
1473 | 6706 | */ |
1474 | 6706 | public boolean isPrimitiveType(Class type) { |
1475 | 6706 | // TODO: this method will probably be deprecated when primitive types |
1476 | 6706 | // are subsumed into the simple type concept |
1477 | 5772 | TypeBindingStrategy.BindingType bindingType |
1478 | 1924 | = configuration.getTypeBindingStrategy().bindingType( type ) ; |
1479 | 5772 | boolean result = (bindingType.equals(TypeBindingStrategy.BindingType.PRIMITIVE)); |
1480 | 5772 | return result; |
1481 | 2758 | } |
1482 | ||
1483 | ||
1484 | /** Some type of pseudo-bean */ |
|
1485 | 3980 | private abstract class BeanType { |
1486 | /** |
|
1487 | * Gets the name for this bean type |
|
1488 | * @return the bean type name, not null |
|
1489 | */ |
|
1490 | public abstract String getBeanName(); |
|
1491 | ||
1492 | /** |
|
1493 | * Gets the type to be used by the associated element |
|
1494 | * @return a Class that is the type not null |
|
1495 | */ |
|
1496 | public abstract Class getElementType(); |
|
1497 | ||
1498 | /** |
|
1499 | * Is this type a primitive? |
|
1500 | * @return true if this type should be treated by betwixt as a primitive |
|
1501 | */ |
|
1502 | public abstract boolean isPrimitiveType(); |
|
1503 | ||
1504 | /** |
|
1505 | * is this type a map? |
|
1506 | * @return true this should be treated as a map. |
|
1507 | */ |
|
1508 | public abstract boolean isMapType(); |
|
1509 | ||
1510 | /** |
|
1511 | * Is this type a loop? |
|
1512 | * @return true if this should be treated as a loop |
|
1513 | */ |
|
1514 | public abstract boolean isLoopType(); |
|
1515 | ||
1516 | /** |
|
1517 | * Gets the properties associated with this bean. |
|
1518 | * @return the BeanProperty's, not null |
|
1519 | */ |
|
1520 | public abstract BeanProperty[] getProperties(); |
|
1521 | ||
1522 | /** |
|
1523 | 0 | * Create string representation |
1524 | * @return something useful for logging |
|
1525 | */ |
|
1526 | public String toString() { |
|
1527 | 0 | return "Bean[name=" + getBeanName() + ", type=" + getElementType(); |
1528 | } |
|
1529 | } |
|
1530 | ||
1531 | /** Supports standard Java Beans */ |
|
1532 | private class JavaBeanType extends BeanType { |
|
1533 | /** Introspected bean */ |
|
1534 | private BeanInfo beanInfo; |
|
1535 | /** Bean class */ |
|
1536 | private Class beanClass; |
|
1537 | /** Bean name */ |
|
1538 | private String name; |
|
1539 | /** Bean properties */ |
|
1540 | private BeanProperty[] properties; |
|
1541 | ||
1542 | 2737 | /** |
1543 | 2737 | * Constructs a BeanType for a standard Java Bean |
1544 | 2737 | * @param beanInfo the BeanInfo describing the standard Java Bean, not null |
1545 | 2737 | */ |
1546 | 5107 | public JavaBeanType(BeanInfo beanInfo) { |
1547 | 2370 | this.beanInfo = beanInfo; |
1548 | 5107 | BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor(); |
1549 | 2370 | beanClass = beanDescriptor.getBeanClass(); |
1550 | 2384 | name = beanDescriptor.getName(); |
1551 | // Array's contain a bad character |
|
1552 | 2370 | if (beanClass.isArray()) { |
1553 | 2737 | // called all array's Array |
1554 | 12 | name = "Array"; |
1555 | } |
|
1556 | ||
1557 | 7844 | } |
1558 | ||
1559 | /** @see BeanType #getElementType */ |
|
1560 | public Class getElementType() { |
|
1561 | 4740 | return beanClass; |
1562 | 2737 | } |
1563 | ||
1564 | /** @see BeanType#getBeanName */ |
|
1565 | public String getBeanName() { |
|
1566 | 2370 | return name; |
1567 | 2737 | } |
1568 | ||
1569 | /** @see BeanType#isPrimitiveType */ |
|
1570 | public boolean isPrimitiveType() { |
|
1571 | 2370 | return XMLIntrospector.this.isPrimitiveType( beanClass ); |
1572 | 2506 | } |
1573 | ||
1574 | /** @see BeanType#isLoopType */ |
|
1575 | public boolean isLoopType() { |
|
1576 | 2172 | return getConfiguration().isLoopType( beanClass ); |
1577 | 147 | } |
1578 | ||
1579 | /** @see BeanType#isMapType */ |
|
1580 | public boolean isMapType() { |
|
1581 | 144 | return Map.class.isAssignableFrom( beanClass ); |
1582 | } |
|
1583 | 2506 | |
1584 | 2506 | /** @see BeanType#getProperties */ |
1585 | public BeanProperty[] getProperties() { |
|
1586 | 2506 | // lazy creation |
1587 | 4678 | if ( properties == null ) { |
1588 | 13022 | ArrayList propertyDescriptors = new ArrayList(); |
1589 | 8344 | // add base bean info |
1590 | 10516 | PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); |
1591 | 10516 | if ( descriptors != null ) { |
1592 | 17710 | for (int i=0, size=descriptors.length; i<size; i++) { |
1593 | 15538 | if (!getConfiguration().getPropertySuppressionStrategy() |
1594 | 8285 | .suppressProperty( |
1595 | 2398 | beanClass, |
1596 | 2398 | descriptors[i].getPropertyType(), |
1597 | 2398 | descriptors[i].getName())) { |
1598 | 5046 | propertyDescriptors.add( descriptors[i] ); |
1599 | } |
|
1600 | 2506 | } |
1601 | 2506 | } |
1602 | 0 | |
1603 | 0 | // add properties from additional bean infos |
1604 | 2172 | BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo(); |
1605 | 2172 | if ( additionals != null ) { |
1606 | 0 | for ( int i=0, outerSize=additionals.length; i<outerSize; i++ ) { |
1607 | 0 | BeanInfo additionalInfo = additionals[i]; |
1608 | 0 | descriptors = beanInfo.getPropertyDescriptors(); |
1609 | 0 | if ( descriptors != null ) { |
1610 | 0 | for (int j=0, innerSize=descriptors.length; j<innerSize; j++) { |
1611 | 0 | if (!getConfiguration().getPropertySuppressionStrategy() |
1612 | 0 | .suppressProperty( |
1613 | 0 | beanClass, |
1614 | 0 | descriptors[j].getPropertyType(), |
1615 | 0 | descriptors[j].getName())) { |
1616 | 0 | propertyDescriptors.add( descriptors[j] ); |
1617 | } |
|
1618 | } |
|
1619 | 2506 | } |
1620 | } |
|
1621 | } |
|
1622 | 2506 | |
1623 | 4678 | addAllSuperinterfaces(beanClass, propertyDescriptors); |
1624 | 8407 | |
1625 | 5901 | // what happens when size is zero? |
1626 | 8073 | properties = new BeanProperty[ propertyDescriptors.size() ]; |
1627 | 2172 | int count = 0; |
1628 | 7230 | for ( Iterator it = propertyDescriptors.iterator(); it.hasNext(); count++) { |
1629 | 7564 | PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it.next(); |
1630 | 5058 | properties[count] = new BeanProperty( propertyDescriptor ); |
1631 | } |
|
1632 | } |
|
1633 | 2172 | return properties; |
1634 | } |
|
1635 | ||
1636 | /** |
|
1637 | * Adds all super interfaces. |
|
1638 | * Super interface methods are not returned within the usual |
|
1639 | * bean info for an interface. |
|
1640 | 2520 | * @param clazz <code>Class</code>, not null |
1641 | 77 | * @param propertyDescriptors <code>ArrayList</code> of <code>PropertyDescriptor</code>s', not null |
1642 | 91 | */ |
1643 | private void addAllSuperinterfaces(Class clazz, ArrayList propertyDescriptors) { |
|
1644 | 2184 | if (clazz.isInterface()) { |
1645 | 66 | Class[] superinterfaces = clazz.getInterfaces(); |
1646 | 92 | for (int i=0, size=superinterfaces.length; i<size; i++) { |
1647 | 0 | try { |
1648 | ||
1649 | BeanInfo beanInfo; |
|
1650 | 26 | if( getConfiguration().ignoreAllBeanInfo() ) { |
1651 | 0 | beanInfo = Introspector.getBeanInfo( superinterfaces[i], Introspector.IGNORE_ALL_BEANINFO ); |
1652 | 14 | } |
1653 | 28 | else { |
1654 | 26 | beanInfo = Introspector.getBeanInfo( superinterfaces[i] ); |
1655 | 14 | } |
1656 | 26 | PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); |
1657 | 38 | for (int j=0, descriptorLength=descriptors.length; j<descriptorLength ; j++) { |
1658 | 26 | if (!getConfiguration().getPropertySuppressionStrategy() |
1659 | 18 | .suppressProperty( |
1660 | 4 | beanClass, |
1661 | 4 | descriptors[j].getPropertyType(), |
1662 | 18 | descriptors[j].getName())) { |
1663 | 12 | propertyDescriptors.add( descriptors[j] ); |
1664 | 0 | } |
1665 | 0 | } |
1666 | 12 | addAllSuperinterfaces(superinterfaces[i], propertyDescriptors); |
1667 | ||
1668 | 0 | } catch (IntrospectionException ex) { |
1669 | 2520 | log.info("Introspection on superinterface failed.", ex); |
1670 | 8 | } |
1671 | } |
|
1672 | } |
|
1673 | 2184 | } |
1674 | ||
1675 | } |
|
1676 | ||
1677 | /** Implementation for DynaClasses */ |
|
1678 | private class DynaClassBeanType extends BeanType { |
|
1679 | /** BeanType for this DynaClass */ |
|
1680 | private DynaClass dynaClass; |
|
1681 | /** Properties extracted in constuctor */ |
|
1682 | private BeanProperty[] properties; |
|
1683 | ||
1684 | 21 | /** |
1685 | 21 | * Constructs a BeanType for a DynaClass |
1686 | 21 | * @param dynaClass not null |
1687 | 21 | */ |
1688 | 95 | public DynaClassBeanType(DynaClass dynaClass) { |
1689 | 74 | this.dynaClass = dynaClass; |
1690 | 18 | DynaProperty[] dynaProperties = dynaClass.getDynaProperties(); |
1691 | 39 | properties = new BeanProperty[dynaProperties.length]; |
1692 | 66 | for (int i=0, size=dynaProperties.length; i<size; i++) { |
1693 | 48 | properties[i] = new BeanProperty(dynaProperties[i]); |
1694 | } |
|
1695 | 39 | } |
1696 | ||
1697 | /** @see BeanType#getBeanName */ |
|
1698 | public String getBeanName() { |
|
1699 | 60 | return dynaClass.getName(); |
1700 | } |
|
1701 | /** @see BeanType#getElementType */ |
|
1702 | public Class getElementType() { |
|
1703 | 57 | return DynaClass.class; |
1704 | } |
|
1705 | /** @see BeanType#isPrimitiveType */ |
|
1706 | public boolean isPrimitiveType() { |
|
1707 | 18 | return false; |
1708 | } |
|
1709 | /** @see BeanType#isMapType */ |
|
1710 | public boolean isMapType() { |
|
1711 | 21 | return false; |
1712 | } |
|
1713 | /** @see BeanType#isLoopType */ |
|
1714 | public boolean isLoopType() { |
|
1715 | 39 | return false; |
1716 | } |
|
1717 | /** @see BeanType#getProperties */ |
|
1718 | public BeanProperty[] getProperties() { |
|
1719 | 18 | return properties; |
1720 | } |
|
1721 | } |
|
1722 | } |