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.ArrayList;
19  import java.util.Arrays;
20  import java.util.HashSet;
21  import java.util.List;
22  import java.util.Locale;
23  import java.util.Set;
24  
25  import net.sf.composite.util.ObjectUtils;
26  import net.sf.morph.reflect.ReflectionException;
27  import net.sf.morph.transform.TransformationException;
28  import net.sf.morph.util.ContainerUtils;
29  import net.sf.morph.util.StringUtils;
30  
31  /**
32   * <p>Copies the properties specified by the <code>propertiesToCopy</code>
33   * property of this class from the source to the destination. If
34   * <code>propertiesToCopy</code> is not specified, all of the source
35   * properties will be copied to the destination.
36   *
37   * <p>Copies properties that have the same name from the source to the destination.
38   * By default, if a property found on the source is missing on the destination,
39   * an exception will <em>not</em> be thrown and copying will continue.  If you
40   * want to ensure all properties from the source are copied to the destination,
41   * set the <em>errorOnMissingProperty</em> property of this class to
42   * <code>true</code>.
43   *
44   * @author Matt Sgarlata
45   * @author Alexander Volanis
46   * @since Oct 31, 2004
47   */
48  public class PropertyNameMatchingCopier extends BasePropertyNameCopier {
49  
50  	private Set propertiesToCopy = ContainerUtils.createOrderedSet();
51  	private Set propertiesToIgnore = ContainerUtils.createOrderedSet();
52  
53  	/**
54  	 * Create a new PropertyNameMatchingCopier.
55  	 */
56  	public PropertyNameMatchingCopier() {
57  		super();
58  		setErrorOnMissingProperty(false);
59  	}
60  
61  	/**
62  	 * Create a new PropertyNameMatchingCopier.
63  	 * @param errorOnMissingProperty
64  	 */
65  	public PropertyNameMatchingCopier(boolean errorOnMissingProperty) {
66  		super(errorOnMissingProperty);
67  	}
68  
69  	/**
70  	 * {@inheritDoc}
71  	 */
72  	public void copyImpl(Object destination, Object source, Locale locale,
73  			Integer preferredTransformationType) throws Exception {
74  		String[] properties = evaluateIncludedProperties(source);
75  		if (log.isInfoEnabled()) {
76  			if (ObjectUtils.isEmpty(properties)) {
77  				getLog().info("No properties available for copying");
78  			}
79  			else {
80  				getLog()
81  						.info("Copying properties " + StringUtils.englishJoin(properties));
82  			}
83  		}
84  
85  		List unreadableProperties = null;
86  		List unwriteableProperties = null;
87  		if (isErrorOnMissingProperty() || getLog().isTraceEnabled()) {
88  			unreadableProperties = new ArrayList();
89  			unwriteableProperties = new ArrayList();
90  		}
91  		for (int i = 0; i < properties.length; i++) {
92  			String property = properties[i];
93  			boolean sourceReadable = getBeanReflector().isReadable(source, property);
94  			boolean destinationWriteable = getBeanReflector().isWriteable(destination,
95  					property);
96  
97  			if (sourceReadable && destinationWriteable) {
98  				copyProperty(property, source, property, destination, locale,
99  						preferredTransformationType);
100 			}
101 			else {
102 				// this check isn't necessary, but is included for performance
103 				// reasons so that we don't construct these strings unnecessarily
104 				if (isErrorOnMissingProperty() || getLog().isTraceEnabled()) {
105 					if (!sourceReadable) {
106 						unreadableProperties.add(property);
107 					}
108 					if (!destinationWriteable) {
109 						unwriteableProperties.add(property);
110 					}
111 				}
112 			}
113 		}
114 
115 		if (isErrorOnMissingProperty() || getLog().isTraceEnabled()) {
116 			int skippedPropertiesSize = unreadableProperties.size()
117 					+ unwriteableProperties.size();
118 			List skippedProperties = new ArrayList(skippedPropertiesSize);
119 			skippedProperties.addAll(unreadableProperties);
120 			skippedProperties.addAll(unwriteableProperties);
121 
122 			String message = "The following properties were not copied "
123 					+ "because they were not readable on the source object, not "
124 					+ "writeable on the destination object or both: "
125 					+ StringUtils.englishJoin(skippedProperties)
126 					+ ".  The properties that were not readable are: "
127 					+ StringUtils.englishJoin(unreadableProperties)
128 					+ ".  The properties that were not writeable are: "
129 					+ StringUtils.englishJoin(unwriteableProperties);
130 			if (isErrorOnMissingProperty()) {
131 				throw new TransformationException(message);
132 			}
133 			// the message is already constructed, so no need for
134 			// another if getLog().isTraceEnabled call
135 			if (!skippedProperties.isEmpty()) {
136 				getLog().trace(message);
137 			}
138 		}
139 	}
140 
141 	/**
142 	 * Get the properties to copy.
143 	 * @return String[]
144 	 */
145 	public synchronized String[] getPropertiesToCopy() {
146 		return (String[]) propertiesToCopy.toArray(new String[propertiesToCopy.size()]);
147 	}
148 
149 	/**
150 	 * Set the properties to copy.
151 	 * @param propertiesToCopy String[]
152 	 */
153 	public synchronized void setPropertiesToCopy(String[] propertiesToCopy) {
154 		this.propertiesToCopy.clear();
155 		this.propertiesToCopy.addAll(Arrays.asList(propertiesToCopy));
156 	}
157 
158 	/**
159 	 * Add a property to copy.
160 	 * @param propertyName
161 	 */
162 	public synchronized void addPropertyToCopy(String propertyName) {
163 		propertiesToCopy.add(propertyName);
164 	}
165 
166 	/**
167 	 * Get the properties to ignore.
168 	 * @return String[]
169 	 */
170 	public synchronized String[] getPropertiesToIgnore() {
171 		return (String[]) propertiesToIgnore
172 				.toArray(new String[propertiesToIgnore.size()]);
173 	}
174 
175 	/**
176 	 * Set the properties to ignore.
177 	 * @param propertiesToIgnore String[]
178 	 */
179 	public synchronized void setPropertiesToIgnore(String[] propertiesToIgnore) {
180 		this.propertiesToIgnore.clear();
181 		this.propertiesToIgnore.addAll(Arrays.asList(propertiesToIgnore));
182 	}
183 
184 	/**
185 	 * Add a property to ignore.
186 	 * @param propertyName
187 	 */
188 	public synchronized void addPropertyToIgnore(String propertyName) {
189 		propertiesToIgnore.add(propertyName);
190 	}
191 
192 	/**
193 	 * {@inheritDoc}
194 	 */
195 	protected boolean isImpreciseTransformationImpl(Class destinationClass,
196 			Class sourceClass) {
197 		//imprecise only if default operation loses information, i.e. properties from source don't exist on dest:
198 		if (!isErrorOnMissingProperty() && ObjectUtils.isEmpty(propertiesToCopy)
199 				&& ObjectUtils.isEmpty(propertiesToIgnore)) {
200 			Object sourceBean;
201 			Object destinationBean;
202 			try {
203 				sourceBean = getInstantiatingReflector().newInstance(sourceClass, null);
204 				destinationBean = getInstantiatingReflector().newInstance(destinationClass, null);
205 			} catch (ReflectionException e) {
206 				return true;
207 			}
208 			HashSet sourcePropertyNames = new HashSet(Arrays.asList(getBeanReflector().getPropertyNames(sourceBean)));
209 			HashSet destinationPropertyNames = new HashSet(Arrays.asList(getBeanReflector().getPropertyNames(destinationBean)));
210 			return !sourcePropertyNames.equals(destinationPropertyNames);
211 		}
212 		return super.isImpreciseTransformationImpl(destinationClass, sourceClass);
213 	}
214 
215 	/**
216 	 * Get the included properties for the given object.
217 	 * @param source
218 	 * @return String[]
219 	 */
220 	private String[] evaluateIncludedProperties(Object source) {
221 		Set result = ContainerUtils.createOrderedSet();
222 		result.addAll(propertiesToCopy);
223 		result.retainAll(propertiesToIgnore);
224 		if (!result.isEmpty()) {
225 			throw new IllegalStateException("Overlapping included/ignored properties: "
226 					+ result);
227 		}
228 		if (ObjectUtils.isEmpty(propertiesToCopy)) {
229 			result.addAll(Arrays.asList(getBeanReflector().getPropertyNames(source)));
230 			result.removeAll(propertiesToIgnore);
231 		}
232 		else {
233 			result = propertiesToCopy;
234 		}
235 		return (String[]) result.toArray(new String[result.size()]);
236 	}
237 }