1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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 }