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.converters;
17  
18  import java.util.Collection;
19  import java.util.Iterator;
20  import java.util.Locale;
21  import java.util.Map;
22  import java.util.Set;
23  
24  import net.sf.composite.util.ObjectUtils;
25  import net.sf.morph.transform.DecoratedConverter;
26  import net.sf.morph.transform.TransformationException;
27  import net.sf.morph.transform.transformers.BaseTransformer;
28  import net.sf.morph.util.BidirectionalMap;
29  import net.sf.morph.util.ClassUtils;
30  import net.sf.morph.util.ContainerUtils;
31  
32  /**
33   * A transformer which transforms defined arbitrary objects to other defined
34   * arbitrary objects based on this class' <code>mapping</code> property.  For
35   * example, an arbitrary mapping might specify that the Integer 1 be converted
36   * to the String "one".  As many mappings as are needed may be specified.  By
37   * default, mappings are assumed to be bidirectional.  Continuing with the
38   * example above, this means that in addition to the Integer 1 being mapped
39   * transformed to the String "one", the String "one" will be transformed to the
40   * Integer 1.
41   * 
42   * @author Matt Sgarlata
43   * @since Apr 11, 2005
44   */
45  public class ArbitraryObjectMappingConverter extends BaseTransformer implements DecoratedConverter {
46  	
47  	private boolean bidirectional = true;
48  	private Map mapping;
49  
50  	/**
51  	 * {@inheritDoc}
52  	 */
53  	protected Object convertImpl(Class destinationClass, Object source,
54  		Locale locale) throws Exception {
55  		if (getMapping().containsKey(source)) {
56  			return getMapping().get(source);
57  		}
58  		if (isBidirectional()) {
59  			BidirectionalMap bidirectionalMap = (BidirectionalMap) mapping;
60  			if (bidirectionalMap.getReverseMap().containsKey(bidirectionalMap)) {
61  				return bidirectionalMap.getKey(source);
62  			}
63  		}
64  		throw new TransformationException(
65  			"No mapping was specified for source object "
66  				+ ObjectUtils.getObjectDescription(source));
67  	}
68  
69  	/**
70  	 * Get the forward classes, plus backward classes if this converter is bidi. 
71  	 * @param forwardMappingKeys
72  	 * @param backwardMappingKeys
73  	 * @return Class[]
74  	 * @throws Exception not likely
75  	 */
76  	protected Class[] getClasses(Collection forwardMappingKeys, Collection backwardMappingKeys) throws Exception {
77  		// pre-calculate the maximum size of the result for efficiency
78  		// this may lead to extra memory costs but will ensure space allocation
79  		// for the destination set will occur only once
80  		int maxNumClasses = forwardMappingKeys.size();
81  		if (isBidirectional()) {
82  			maxNumClasses += backwardMappingKeys.size();
83  		}
84  
85  		Set classes = ContainerUtils.createOrderedSet();
86  		addContainedClasses(classes, forwardMappingKeys);
87  
88  		if (isBidirectional()) {
89  			addContainedClasses(classes, backwardMappingKeys);
90  		}
91  
92  		return (Class[]) classes.toArray(new Class[classes.size()]); 
93  	}
94  
95  	/**
96  	 * Add the classes of the contents of <code>objects</code> to <code>classes</code>.
97  	 * @param classes
98  	 * @param objects
99  	 */
100 	protected void addContainedClasses(Set classes, Collection objects) {
101 		if (objects != null) {
102 			for (Iterator i = objects.iterator(); i.hasNext(); ) {
103 				classes.add(ClassUtils.getClass(i.next()));
104 			}
105 		}
106 	}
107 
108 	/**
109 	 * {@inheritDoc}
110 	 */
111 	protected Class[] getSourceClassesImpl() throws Exception {
112 		if (ObjectUtils.isEmpty(mapping)) {
113 			throw new IllegalStateException("The mapping property of this converter must be set");
114 		}
115 		return getClasses(mapping.keySet(), mapping.values());
116 	}
117 
118 	/**
119 	 * {@inheritDoc}
120 	 */
121 	protected Class[] getDestinationClassesImpl() throws Exception {
122 		if (ObjectUtils.isEmpty(mapping)) {
123 			throw new IllegalStateException("The mapping property of this converter must be set");
124 		}
125 		return getClasses(mapping.values(), mapping.keySet());
126 	}
127 
128 	/**
129 	 * {@inheritDoc}
130 	 */
131 	protected boolean isWrappingRuntimeExceptions() {
132 	    return true;
133     }
134 
135 	/**
136 	 * Learn whether this ArbitraryObjectMappingConverter is bidirectional.
137 	 * @return boolean
138 	 */
139 	public boolean isBidirectional() {
140 		return bidirectional;
141 	}
142 
143 	/**
144 	 * Set whether this ArbitraryObjectMappingConverter is bidirectional.
145 	 * @param bidirectional
146 	 */
147 	public void setBidirectional(boolean bidirectional) {
148 		setInitialized(false);
149 		this.bidirectional = bidirectional;
150 	}
151 
152 	/**
153 	 * Get the object mapping.
154 	 * @return Map
155 	 */
156 	public Map getMapping() {
157 		return mapping;
158 	}
159 
160 	/**
161 	 * Set the mapping.
162 	 * 
163 	 * @param mapping the mapping
164 	 * @throws IllegalArgumentException
165 	 *             if <code>bidirectional</code> is <code>true</code> and
166 	 *             there is some value which is mapped to more than one key. In
167 	 *             this case, it is impossible to construct a bidirectional map
168 	 *             because there is no way to determine to which key the value
169 	 *             is mapped.
170 	 */
171 	public void setMapping(Map mapping) {
172 		setInitialized(false);
173 		if (isBidirectional() && !(mapping instanceof BidirectionalMap)) {
174 			this.mapping = new BidirectionalMap(mapping);
175 		}
176 		else {
177 			this.mapping = mapping;			
178 		}
179 	}
180 
181 	/**
182 	 * @return Map
183 	 * @deprecated
184 	 */
185 	public Map getVisitedSourceToDestinationMap() {
186 		return getMapping();
187 	}
188 
189 	/**
190 	 * @param visitedSourceToDestinationMap
191 	 * @deprecated
192 	 */
193 	public void setVisitedSourceToDestinationMap(Map visitedSourceToDestinationMap) {
194 		throw new UnsupportedOperationException();
195 	}
196 
197 }