1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package de.fu_berlin.ties.io;
23
24 import java.lang.reflect.Constructor;
25 import java.lang.reflect.InvocationTargetException;
26 import java.util.Iterator;
27 import java.util.LinkedHashMap;
28 import java.util.Map;
29
30 import org.dom4j.Attribute;
31 import org.dom4j.Element;
32
33 import de.fu_berlin.ties.xml.dom.DOMUtils;
34
35 /***
36 * A map targeted at serialization and deserialiation of objects in
37 * human-readable formats. It that allows creating (deserializing) an object
38 * by passing itself to the constructor.
39 *
40 * <p>This class preserves the guarantees of {@link java.util.LinkedHashMap}:
41 * it allows both efficient retrieval of values by key/field name (due to the
42 * contained hash map) and iteration of key/value pairs in order of insertion
43 * (due to the contained linked list).
44 *
45 * <p><strong>Note that this implementation is not synchronized.</strong>
46 * If multiple threads access a field map concurrently, and at least one of the
47 * threads modifies the map structurally, it <em>must</em> be synchronized
48 * externally. This is typically accomplished by synchronizing on some object
49 * that naturally encapsulates the map. If no such object exists, the map
50 * should be "wrapped" using the
51 * {@link java.util.Collections#synchronizedMap(java.util.Map)} method.
52 *
53 * @author Christian Siefkes
54 * @version $Revision: 1.9 $, $Date: 2006/10/21 16:04:22 $, $Author: siefkes $
55 */
56 public class FieldMap extends LinkedHashMap<String, Object>
57 implements XMLStorable {
58
59 /***
60 * The number of the section this field map is in. Generally, a
61 * {@link FieldContainer}s contains one or multiple sections which are
62 * numbered sequentially starting from 0. In case of the
63 * {@linkplain DelimSepValues DSV format}, sections are separated by
64 * empty lines.
65 */
66 private final int section;
67
68
69 /***
70 * Creates a new instance, setting the {@linkplain #getSection() section
71 * number} to 0.
72 */
73 public FieldMap() {
74 this(0);
75 }
76
77 /***
78 * Creates a new instance with the same mappings as the specified map,
79 * setting the {@linkplain #getSection() section number} to 0.
80 *
81 * @param map the map whose mappings are to be placed in this map
82 * @throws NullPointerException if the specified map is null
83 */
84 public FieldMap(final Map<String, Object> map) throws NullPointerException {
85 this(map, 0);
86 }
87
88 /***
89 * Convenience constructor that delegates to the {@link #FieldMap() standard
90 * constructor} and then puts a first key/value pair into the map.
91 *
92 * @param firstKey the key of the first key/value pair
93 * @param firstValue the value of the first key/value pair
94 */
95 public FieldMap(final String firstKey, final Object firstValue) {
96 this();
97 put(firstKey, firstValue);
98 }
99
100 /***
101 * Creates a new instance.
102 * @param sectionNo the number of the {@linkplain #getSection() section}
103 * this field map is in
104 */
105 public FieldMap(final int sectionNo) {
106 super();
107 section = sectionNo;
108 }
109
110 /***
111 * Creates a new instance with the same mappings as the specified map.
112 *
113 * @param map the map whose mappings are to be placed in this map
114 * @param sectionNo the number of the {@linkplain #getSection() section}
115 * this field map is in
116 * @throws NullPointerException if the specified map is null
117 */
118 public FieldMap(final Map<String, Object> map, final int sectionNo)
119 throws NullPointerException {
120 super(map);
121 section = sectionNo;
122 }
123
124
125 /***
126 * Creates a new instance from an XML element, fulfilling the
127 * recommandation of the {@link XMLStorable} interface.
128 * The {@linkplain #getSection() section number} is set to 0.
129 *
130 * @param element the XML element containing the serialized representation
131 * @throws IllegalArgumentException if the element contains child elements
132 */
133 public FieldMap(final Element element)
134 throws IllegalArgumentException {
135 this(element, 0);
136 }
137
138 /***
139 * Creates a new instance from an XML element, fulfilling the
140 * recommandation of the {@link XMLStorable} interface.
141 *
142 * @param element the XML element containing the serialized representation
143 * @param sectionNo the number of the {@linkplain #getSection() section}
144 * this field map is in
145 * @throws IllegalArgumentException if the element contains child elements
146 */
147 public FieldMap(final Element element, final int sectionNo)
148 throws IllegalArgumentException {
149 this(sectionNo);
150 if (element != null) {
151
152 if (element.elementIterator().hasNext()) {
153 throw new IllegalArgumentException("Cannot create a FieldMap "
154 + "from an element with child elements: " + element);
155 }
156
157
158 put(element.getName(), element.getText());
159
160
161 final Iterator attribIter = element.attributeIterator();
162 Attribute attrib;
163
164 while (attribIter.hasNext()) {
165 attrib = (Attribute) attribIter.next();
166 put(attrib.getName(), attrib.getValue());
167 }
168 }
169 }
170
171
172 /***
173 * Creates (deserializes) an object of a specified type by calling a
174 * constructor of the class that accepts a field map as single argument
175 * and passing itself as parameter. Only classes that provide a suitable
176 * constructor can be instantiated this way.
177 *
178 * @param type the class of the object to create; must have a constructor
179 * whose only argument is a <code>FieldMap</code>
180 * @return the created object; the returned object will be an instance of
181 * the specified class
182 * @throws InstantiationException if instantiation failed
183 * @throws SecurityException if access to the required reflection
184 * information is denied
185 */
186 public Object createObject(final Class type) throws InstantiationException,
187 SecurityException {
188 try {
189
190 final Constructor cons = type.getConstructor(
191 new Class[] {FieldMap.class});
192
193 return cons.newInstance(new Object[] {this});
194 } catch (IllegalAccessException iae) {
195 final InstantiationException ie =
196 new InstantiationException(iae.toString());
197 ie.initCause(iae);
198 throw ie;
199 } catch (InvocationTargetException ite) {
200 final InstantiationException ie =
201 new InstantiationException(ite.toString());
202 ie.initCause(ite);
203 throw ie;
204 } catch (ExceptionInInitializerError eiie) {
205 final InstantiationException ie =
206 new InstantiationException(eiie.toString());
207 ie.initCause(eiie);
208 throw ie;
209 } catch (NoSuchMethodException nsme) {
210 final InstantiationException ie =
211 new InstantiationException(nsme.toString());
212 ie.initCause(nsme);
213 throw ie;
214 }
215 }
216
217 /***
218 * Returns the number of the section this field map is in. Generally, a
219 * {@link FieldContainer}s contains one or multiple sections which are
220 * numbered sequentially starting from 0. In case of the
221 * {@linkplain DelimSepValues DSV format}, sections are separated by
222 * empty lines.
223 *
224 * @return the section number
225 */
226 public int getSection() {
227 return section;
228 }
229
230 /***
231 * {@inheritDoc} This implementation delegates to
232 * {@link #toElement(boolean)}, setting the Java attribute.
233 */
234 public ObjectElement toElement() {
235 return toElement(true);
236 }
237
238 /***
239 * Stores all relevant fields of this object in an XML element for
240 * serialization. The key of the first field is used to name the element,
241 * the corresponding value is stored in the textual content of the.
242 * Keys and values of all further fields are stored in attribute name/value
243 * pairs. If this field map is empty, <code>null</code> is returned.
244 *
245 * @param setJavaAttrib whether to store the class of this object in the
246 * {@link ObjectElement#JAVA_CLASS_ATTRIBUTE} attribute; if
247 * <code>false</code> this attribute is not used
248 * @return the created XML element
249 */
250 public ObjectElement toElement(final boolean setJavaAttrib) {
251 final Iterator<Map.Entry<String, Object>> entryIter
252 = entrySet().iterator();
253 Map.Entry<String, Object> entry;
254 final ObjectElement result;
255
256 if (entryIter.hasNext()) {
257
258 final Class javaClass =
259 setJavaAttrib ? this.getClass() : null;
260
261
262 entry = entryIter.next();
263 result = new ObjectElement(DOMUtils.defaultName(entry.getKey()),
264 javaClass);
265 if (entry.getValue() != null) {
266 result.addText(entry.getValue().toString());
267 }
268
269
270 while (entryIter.hasNext()) {
271 entry = entryIter.next();
272 if (entry.getValue() != null) {
273 result.addAttribute(DOMUtils.defaultName(entry.getKey()),
274 entry.getValue().toString());
275 }
276 }
277 } else {
278
279 result = null;
280 }
281 return result;
282 }
283
284 }