001: package org.apache.commons.betwixt;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one or more
005: * contributor license agreements. See the NOTICE file distributed with
006: * this work for additional information regarding copyright ownership.
007: * The ASF licenses this file to You under the Apache License, Version 2.0
008: * (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019:
020: import java.beans.PropertyDescriptor;
021: import java.lang.reflect.Method;
022: import java.util.Map;
023:
024: import org.apache.commons.beanutils.DynaProperty;
025: import org.apache.commons.betwixt.expression.DynaBeanExpression;
026: import org.apache.commons.betwixt.expression.DynaBeanUpdater;
027: import org.apache.commons.betwixt.expression.Expression;
028: import org.apache.commons.betwixt.expression.IteratorExpression;
029: import org.apache.commons.betwixt.expression.MethodExpression;
030: import org.apache.commons.betwixt.expression.MethodUpdater;
031: import org.apache.commons.betwixt.expression.Updater;
032: import org.apache.commons.betwixt.strategy.NameMapper;
033: import org.apache.commons.betwixt.strategy.SimpleTypeMapper;
034: import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
035: import org.apache.commons.logging.Log;
036:
037: /**
038: * Betwixt-centric view of a bean (or pseudo-bean) property.
039: * This object decouples the way that the (possibly pseudo) property introspection
040: * is performed from the results of that introspection.
041: *
042: * @author Robert Burrell Donkin
043: * @since 0.5
044: */
045: public class BeanProperty {
046:
047: /** The bean name for the property (not null) */
048: private final String propertyName;
049: /** The type of this property (not null) */
050: private final Class propertyType;
051: /** The Expression used to read values of this property (possibly null) */
052: private Expression propertyExpression;
053: /** The Updater used to write values of this property (possibly null) */
054: private Updater propertyUpdater;
055:
056: /**
057: * Construct a BeanProperty.
058: * @param propertyName not null
059: * @param propertyType not null
060: * @param propertyExpression the Expression used to read the property,
061: * null if the property is not readable
062: * @param propertyUpdater the Updater used to write the property,
063: * null if the property is not writable
064: */
065: public BeanProperty(String propertyName, Class propertyType,
066: Expression propertyExpression, Updater propertyUpdater) {
067: this .propertyName = propertyName;
068: this .propertyType = propertyType;
069: this .propertyExpression = propertyExpression;
070: this .propertyUpdater = propertyUpdater;
071: }
072:
073: /**
074: * Constructs a BeanProperty from a <code>PropertyDescriptor</code>.
075: * @param descriptor not null
076: */
077: public BeanProperty(PropertyDescriptor descriptor) {
078: this .propertyName = descriptor.getName();
079: this .propertyType = descriptor.getPropertyType();
080:
081: Method readMethod = descriptor.getReadMethod();
082: if (readMethod != null) {
083: this .propertyExpression = new MethodExpression(readMethod);
084: }
085:
086: Method writeMethod = descriptor.getWriteMethod();
087: if (writeMethod != null) {
088: this .propertyUpdater = new MethodUpdater(writeMethod);
089: }
090: }
091:
092: /**
093: * Constructs a BeanProperty from a <code>DynaProperty</code>
094: * @param dynaProperty not null
095: */
096: public BeanProperty(DynaProperty dynaProperty) {
097: this .propertyName = dynaProperty.getName();
098: this .propertyType = dynaProperty.getType();
099: this .propertyExpression = new DynaBeanExpression(propertyName);
100: this .propertyUpdater = new DynaBeanUpdater(propertyName,
101: propertyType);
102: }
103:
104: /**
105: * Gets the bean name for this property.
106: * Betwixt will map this to an xml name.
107: * @return the bean name for this property, not null
108: */
109: public String getPropertyName() {
110: return propertyName;
111: }
112:
113: /**
114: * Gets the type of this property.
115: * @return the property type, not null
116: */
117: public Class getPropertyType() {
118: return propertyType;
119: }
120:
121: /**
122: * Gets the expression used to read this property.
123: * @return the expression to be used to read this property
124: * or null if this property is not readable.
125: */
126: public Expression getPropertyExpression() {
127: return propertyExpression;
128: }
129:
130: /**
131: * Gets the updater used to write to this properyty.
132: * @return the Updater to the used to write to this property
133: * or null if this property is not writable.
134: */
135: public Updater getPropertyUpdater() {
136: return propertyUpdater;
137: }
138:
139: /**
140: * Create a XML descriptor from a bean one.
141: * Go through and work out whether it's a loop property, a primitive or a standard.
142: * The class property is ignored.
143: *
144: * @param configuration <code>IntrospectionConfiguration</code>, not null
145: * @return a correctly configured <code>NodeDescriptor</code> for the property
146: */
147: public Descriptor createXMLDescriptor(
148: IntrospectionConfiguration configuration) {
149: Log log = configuration.getIntrospectionLog();
150: if (log.isTraceEnabled()) {
151: log.trace("Creating descriptor for property: name="
152: + getPropertyName() + " type=" + getPropertyType());
153: }
154:
155: NodeDescriptor descriptor = null;
156: Expression propertyExpression = getPropertyExpression();
157: Updater propertyUpdater = getPropertyUpdater();
158:
159: if (propertyExpression == null) {
160: if (log.isTraceEnabled()) {
161: log.trace("No read method for property: name="
162: + getPropertyName() + " type="
163: + getPropertyType());
164: }
165: return null;
166: }
167:
168: if (log.isTraceEnabled()) {
169: log.trace("Property expression=" + propertyExpression);
170: }
171:
172: // choose response from property type
173:
174: //TODO this big conditional should be replaced with subclasses based
175: // on the type
176:
177: //TODO complete simple type implementation
178: TypeBindingStrategy.BindingType bindingType = configuration
179: .getTypeBindingStrategy()
180: .bindingType(getPropertyType());
181: if (bindingType
182: .equals(TypeBindingStrategy.BindingType.PRIMITIVE)) {
183: descriptor = createDescriptorForPrimitive(configuration,
184: propertyExpression, propertyUpdater);
185:
186: } else if (configuration.isLoopType(getPropertyType())) {
187:
188: if (log.isTraceEnabled()) {
189: log.trace("Loop type: " + getPropertyName());
190: log.trace("Wrap in collections? "
191: + configuration.isWrapCollectionsInElement());
192: }
193:
194: if (Map.class.isAssignableFrom(getPropertyType())) {
195: descriptor = createDescriptorForMap(configuration,
196: propertyExpression);
197: } else {
198:
199: descriptor = createDescriptorForCollective(
200: configuration, propertyUpdater,
201: propertyExpression);
202: }
203: } else {
204: if (log.isTraceEnabled()) {
205: log.trace("Standard property: " + getPropertyName());
206: }
207: descriptor = createDescriptorForStandard(
208: propertyExpression, propertyUpdater, configuration);
209: }
210:
211: if (log.isTraceEnabled()) {
212: log.trace("Created descriptor:");
213: log.trace(descriptor);
214: }
215: return descriptor;
216: }
217:
218: /**
219: * Configures descriptor (in the standard way).
220: * This sets the common properties.
221: *
222: * @param propertyName the name of the property mapped to the Descriptor, not null
223: * @param propertyType the type of the property mapped to the Descriptor, not null
224: * @param descriptor Descriptor to map, not null
225: * @param configuration IntrospectionConfiguration, not null
226: */
227: private void configureDescriptor(NodeDescriptor descriptor,
228: IntrospectionConfiguration configuration) {
229: NameMapper nameMapper = configuration.getElementNameMapper();
230: if (descriptor instanceof AttributeDescriptor) {
231: // we want to use the attributemapper only when it is an attribute..
232: nameMapper = configuration.getAttributeNameMapper();
233:
234: }
235: descriptor.setLocalName(nameMapper
236: .mapTypeToElementName(propertyName));
237: descriptor.setPropertyName(getPropertyName());
238: descriptor.setPropertyType(getPropertyType());
239: }
240:
241: /**
242: * Creates an <code>ElementDescriptor</code> for a standard property
243: * @param propertyExpression
244: * @param propertyUpdater
245: * @return
246: */
247: private ElementDescriptor createDescriptorForStandard(
248: Expression propertyExpression, Updater propertyUpdater,
249: IntrospectionConfiguration configuration) {
250:
251: ElementDescriptor result;
252:
253: ElementDescriptor elementDescriptor = new ElementDescriptor();
254: elementDescriptor.setContextExpression(propertyExpression);
255: if (propertyUpdater != null) {
256: elementDescriptor.setUpdater(propertyUpdater);
257: }
258:
259: elementDescriptor.setHollow(true);
260:
261: result = elementDescriptor;
262:
263: configureDescriptor(result, configuration);
264: return result;
265: }
266:
267: /**
268: * Creates an ElementDescriptor for an <code>Map</code> type property
269: * @param configuration
270: * @param propertyExpression
271: * @return
272: */
273: private ElementDescriptor createDescriptorForMap(
274: IntrospectionConfiguration configuration,
275: Expression propertyExpression) {
276:
277: //TODO: need to clean the element descriptors so that the wrappers are plain
278: ElementDescriptor result;
279:
280: ElementDescriptor entryDescriptor = new ElementDescriptor();
281: entryDescriptor.setContextExpression(new IteratorExpression(
282: propertyExpression));
283:
284: entryDescriptor.setLocalName("entry");
285: entryDescriptor.setPropertyName(getPropertyName());
286: entryDescriptor.setPropertyType(getPropertyType());
287:
288: // add elements for reading
289: ElementDescriptor keyDescriptor = new ElementDescriptor("key");
290: keyDescriptor.setHollow(true);
291: entryDescriptor.addElementDescriptor(keyDescriptor);
292:
293: ElementDescriptor valueDescriptor = new ElementDescriptor(
294: "value");
295: valueDescriptor.setHollow(true);
296: entryDescriptor.addElementDescriptor(valueDescriptor);
297:
298: if (configuration.isWrapCollectionsInElement()) {
299: ElementDescriptor wrappingDescriptor = new ElementDescriptor();
300: wrappingDescriptor
301: .setElementDescriptors(new ElementDescriptor[] { entryDescriptor });
302: NameMapper nameMapper = configuration
303: .getElementNameMapper();
304: wrappingDescriptor.setLocalName(nameMapper
305: .mapTypeToElementName(propertyName));
306: result = wrappingDescriptor;
307:
308: } else {
309: result = entryDescriptor;
310: }
311: result.setCollective(true);
312: return result;
313: }
314:
315: /**
316: * Creates an <code>ElementDescriptor</code> for a collective type property
317: * @param configuration
318: * @param propertyUpdater, <code>Updater</code> for the property, possibly null
319: * @param propertyExpression
320: * @return
321: */
322: private ElementDescriptor createDescriptorForCollective(
323: IntrospectionConfiguration configuration,
324: Updater propertyUpdater, Expression propertyExpression) {
325:
326: ElementDescriptor result;
327:
328: ElementDescriptor loopDescriptor = new ElementDescriptor();
329: loopDescriptor.setContextExpression(new IteratorExpression(
330: propertyExpression));
331: loopDescriptor.setPropertyName(getPropertyName());
332: loopDescriptor.setPropertyType(getPropertyType());
333: loopDescriptor.setHollow(true);
334: // set the property updater (if it exists)
335: // may be overridden later by the adder
336: loopDescriptor.setUpdater(propertyUpdater);
337: loopDescriptor.setCollective(true);
338:
339: if (configuration.isWrapCollectionsInElement()) {
340: // create wrapping desctiptor
341: ElementDescriptor wrappingDescriptor = new ElementDescriptor();
342: wrappingDescriptor
343: .setElementDescriptors(new ElementDescriptor[] { loopDescriptor });
344: wrappingDescriptor.setLocalName(configuration
345: .getElementNameMapper().mapTypeToElementName(
346: propertyName));
347: result = wrappingDescriptor;
348:
349: } else {
350: // unwrapped Descriptor
351: result = loopDescriptor;
352: }
353: return result;
354: }
355:
356: /**
357: * Creates a NodeDescriptor for a primitive type node
358: * @param configuration
359: * @param name
360: * @param log
361: * @param propertyExpression
362: * @param propertyUpdater
363: * @return
364: */
365: private NodeDescriptor createDescriptorForPrimitive(
366: IntrospectionConfiguration configuration,
367: Expression propertyExpression, Updater propertyUpdater) {
368: Log log = configuration.getIntrospectionLog();
369: NodeDescriptor descriptor;
370: if (log.isTraceEnabled()) {
371: log.trace("Primitive type: " + getPropertyName());
372: }
373: SimpleTypeMapper.Binding binding = configuration
374: .getSimpleTypeMapper().bind(propertyName, propertyType,
375: configuration);
376: if (SimpleTypeMapper.Binding.ATTRIBUTE.equals(binding)) {
377: if (log.isTraceEnabled()) {
378: log.trace("Adding property as attribute: "
379: + getPropertyName());
380: }
381: descriptor = new AttributeDescriptor();
382: } else {
383: if (log.isTraceEnabled()) {
384: log.trace("Adding property as element: "
385: + getPropertyName());
386: }
387: descriptor = new ElementDescriptor();
388: }
389: descriptor.setTextExpression(propertyExpression);
390: if (propertyUpdater != null) {
391: descriptor.setUpdater(propertyUpdater);
392: }
393: configureDescriptor(descriptor, configuration);
394: return descriptor;
395: }
396: }
|