View Javadoc

1   /*
2    * Copyright (C) 2003-2006 Christian Siefkes <christian@siefkes.net>.
3    * Development of this software is supported by the German Research Society,
4    * Berlin-Brandenburg Graduate School in Distributed Information Systems
5    * (DFG grant no. GRK 316).
6    *
7    * This program is free software; you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as published by
9    * the Free Software Foundation; either version 2 of the License, or
10   * (at your option) any later version.
11   *
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with this program; if not, visit
19   * http://www.gnu.org/licenses/gpl.html or write to the Free Software
20   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
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             // ensure there are no child elements
152             if (element.elementIterator().hasNext()) {
153                 throw new IllegalArgumentException("Cannot create a FieldMap "
154                         + "from an element with child elements: " + element);
155             }
156 
157             // store local name of element + textual content as first pair
158             put(element.getName(), element.getText());
159 
160             // store local names + values of attributes as further pairs
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             // load constructor with FieldMap as only argument
190             final Constructor cons = type.getConstructor(
191                     new Class[] {FieldMap.class});
192             // pass myself as single argument
193             return cons.newInstance(new Object[] {this});
194         } catch (IllegalAccessException iae) {        // repackage exception
195             final InstantiationException ie =
196                 new InstantiationException(iae.toString());
197             ie.initCause(iae);
198             throw ie;
199         } catch (InvocationTargetException ite) {     // repackage exception
200             final InstantiationException ie =
201                 new InstantiationException(ite.toString());
202             ie.initCause(ite);
203             throw ie;
204         } catch (ExceptionInInitializerError eiie) {  // repackage exception
205             final InstantiationException ie =
206                 new InstantiationException(eiie.toString());
207             ie.initCause(eiie);
208             throw ie;
209         } catch (NoSuchMethodException nsme) {        // repackage exception
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             // set java class to null if it should not be used
258             final Class javaClass =
259                 setJavaAttrib ? this.getClass() : null;
260 
261             // use first key as element name, value as textual content
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             // store further key/value pairs as attributes
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             // empty map
279             result = null;
280         }
281         return result;
282     }
283 
284 }