View Javadoc

1   /*
2    * Copyright 2004-2005, 2008 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    * 
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package net.sf.morph.transform.copiers;
17  
18  import java.util.Collection;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.Locale;
22  import java.util.Map;
23  import java.util.Set;
24  import java.util.Map.Entry;
25  
26  import net.sf.composite.util.ObjectUtils;
27  import net.sf.morph.transform.TransformationException;
28  import net.sf.morph.util.ContainerUtils;
29  
30  /**
31   * Copies properties from the source to the destination based on a mapping of
32   * property names.
33   * 
34   * <p>
35   * By default, property name mappings are considered bidirectional, so if you
36   * call <code>addMapping("foo", "bar")</code>, the copy method will copy the
37   * value of <code>foo</code> to <code>bar</code> and will also copy
38   * <code>bar</code> to <code>foo</code>. Mappings specified by the
39   * <code>setMapping</code> method are considered bidirectional as well. If
40   * this behavior is not desired, just set the <code>bidirectional</code>
41   * property to false.
42   * 
43   * <p>
44   * If a property in the <code>mapping</code> cannot be copied, by default a
45   * <code>TransformationException</code> will be thrown. To turn off this
46   * behavior, set <code>errorOnMissingProperty</code> to <code>false</code>.
47   * 
48   * @author Matt Sgarlata
49   * @author Alexander Volanis
50   * @since Jan 25, 2005
51   */
52  public class PropertyNameMappingCopier extends BasePropertyNameCopier {
53  
54  	private Map mapping;
55  	private boolean bidirectional = true;
56  
57  	/**
58  	 * Create a new PropertyNameMappingCopier.
59  	 */
60  	public PropertyNameMappingCopier() {
61  		super();
62  		setErrorOnMissingProperty(true);
63  	}
64  
65  	/**
66  	 * Create a new PropertyNameMappingCopier.
67  	 * @param errorOnMissingProperty
68  	 */
69  	public PropertyNameMappingCopier(boolean errorOnMissingProperty) {
70  		super(errorOnMissingProperty);
71  	}
72  
73  	/**
74  	 * {@inheritDoc}
75  	 */
76  	protected void initializeImpl() throws Exception {
77  		super.initializeImpl();
78  		if (ObjectUtils.isEmpty(mapping)) {
79  			throw new TransformationException(
80  					"You must specify which properties you would like the "
81  							+ getClass().getName()
82  							+ " to copy by setting the mapping property");
83  		}
84  		ensureOnlyStrings(mapping.keySet());
85  		ensureOnlyStrings(mapping.values());
86  		if (bidirectional) {
87  			// make sure there are no duplicate mappings
88  			Set propertiesWithMappings = ContainerUtils.createOrderedSet();
89  			Iterator iterator = mapping.values().iterator();
90  			while (iterator.hasNext()) {
91  				String propertyName = (String) iterator.next();
92  				if (propertiesWithMappings.contains(propertyName)) {
93  					throw new TransformationException(
94  							"A duplicate mapping was detected for property '"
95  									+ propertyName
96  									+ "'.  Please remote the duplicate mapping or set bidirectional to false.");
97  				}
98  				propertiesWithMappings.add(propertyName);
99  			}
100 		}
101 	}
102 
103 	/**
104 	 * Ensure all collection entries are Strings.
105 	 * @param collection
106 	 */
107 	private void ensureOnlyStrings(Collection collection) {
108 		for (Iterator i = collection.iterator(); i.hasNext();) {
109 			Object value = i.next();
110 			if (!(value instanceof String)) {
111 				throw new TransformationException(
112 						"An invalid mapping element was specified: "
113 								+ ObjectUtils.getObjectDescription(value)
114 								+ ".  Mapping elements must be Strings");
115 			}
116 		}
117 	}
118 
119 	/**
120 	 * {@inheritDoc}
121 	 */
122 	protected void copyImpl(Object destination, Object source, Locale locale,
123 			Integer preferredTransformationType) throws TransformationException {
124 
125 		Entry propertyMapping;
126 		String sourceProperty;
127 		String destinationProperty;
128 		boolean missingProperty;
129 
130 		for (Iterator i = mapping.entrySet().iterator(); i.hasNext();) {
131 			propertyMapping = (Entry) i.next();
132 			sourceProperty = (String) propertyMapping.getKey();
133 			destinationProperty = (String) propertyMapping.getValue();
134 			missingProperty = false;
135 
136 			if (getBeanReflector().isReadable(source, sourceProperty)
137 					&& getBeanReflector().isWriteable(destination, destinationProperty)) {
138 				copyProperty(sourceProperty, source, destinationProperty, destination,
139 						locale, preferredTransformationType);
140 			}
141 			// try copying in reverse order for bidirectional processing
142 			else if (isBidirectional()
143 					&& getBeanReflector().isReadable(source, destinationProperty)
144 					&& getBeanReflector().isWriteable(destination, sourceProperty)) {
145 				copyProperty(destinationProperty, source, sourceProperty, destination,
146 						locale, preferredTransformationType);
147 			}
148 			else {
149 				missingProperty = true;
150 			}
151 
152 			if (missingProperty
153 					&& (getLog().isWarnEnabled() || isErrorOnMissingProperty())) {
154 				String message = "Failed to copy property '" + sourceProperty + "' of "
155 						+ ObjectUtils.getObjectDescription(source) + " to property '"
156 						+ destinationProperty + "' of  "
157 						+ ObjectUtils.getObjectDescription(destination);
158 				if (isErrorOnMissingProperty()) {
159 					throw new TransformationException(message);
160 				}
161 				getLog().warn(message);
162 			}
163 		}
164 	}
165 
166 	/**
167 	 * Get the property mapping.
168 	 * @return Map
169 	 */
170 	protected Map getMapping() {
171 		return mapping;
172 	}
173 
174 	/**
175 	 * Set the property mapping.
176 	 * @param mapping
177 	 */
178 	public void setMapping(Map mapping) {
179 		this.mapping = mapping;
180 	}
181 
182 	/**
183 	 * Add a single mapping.
184 	 * @param sourcePropertyName
185 	 * @param destinationPropertyName
186 	 */
187 	public void addMapping(String sourcePropertyName, String destinationPropertyName) {
188 		if (mapping == null) {
189 			mapping = new HashMap();
190 		}
191 		mapping.put(sourcePropertyName, destinationPropertyName);
192 	}
193 
194 	/**
195 	 * Learn whether this copier is bidirectional.
196 	 * @return boolean
197 	 */
198 	public boolean isBidirectional() {
199 		return bidirectional;
200 	}
201 
202 	/**
203 	 * Set whether this copier is bidirectional.
204 	 * @param bidirectional
205 	 */
206 	public void setBidirectional(boolean bidirectional) {
207 		this.bidirectional = bidirectional;
208 	}
209 }