Coverage Report - net.sf.practicalxml.converter.CollectionConverter
 
Classes in this File Line Coverage Branch Coverage Complexity
CollectionConverter
94%
64/68
94%
36/38
3.3
 
 1  
 // Copyright 2008-2014 severally by the contributors
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package net.sf.practicalxml.converter;
 16  
 
 17  
 import java.util.ArrayList;
 18  
 import java.util.Collection;
 19  
 import java.util.HashMap;
 20  
 import java.util.HashSet;
 21  
 import java.util.List;
 22  
 import java.util.Map;
 23  
 import java.util.Set;
 24  
 
 25  
 import javax.xml.namespace.QName;
 26  
 
 27  
 import org.w3c.dom.Document;
 28  
 import org.w3c.dom.Element;
 29  
 
 30  
 import net.sf.practicalxml.DomUtil;
 31  
 
 32  
 
 33  
 /**
 34  
  *  Converts between an XML DOM and hierarchical Java collections, according
 35  
  *  to the following rules:
 36  
  *  <p>
 37  
  *  From DOM to Collection:
 38  
  *  <ul>
 39  
  *  <li> The top-level <code>Element</code> is transformed into <code>Map</code>.
 40  
  *  <li> The keys of this map will be the <em>local names</em> of the child
 41  
  *       elements, sans prefix.
 42  
  *  <li> Children that have text content will map to <code>String</code> values.
 43  
  *  <li> Children that have element content will map to <code>Map</code> values,
 44  
  *       and processed recursively.
 45  
  *  <li> Mixed content is not permitted; if an element contains mixed content,
 46  
  *       the text nodes will be discarded.
 47  
  *  <li> If multiple child elements have the same name, they will map to a
 48  
  *       <code>List</code>, which in turn contains either strings or maps.
 49  
  *  <li> Empty elements are added to the map with a value of <code>null</code>.
 50  
  *  <li> Order of keys is not preserved, but order of repeated elements is.
 51  
  *  </ul>
 52  
  *  <p>
 53  
  *  From  Collection to DOM:
 54  
  *  <ul>
 55  
  *  <li> A <code>Map</code> is converted to an element with children, where each
 56  
  *       key in the map becomes an <code>Element</code>.
 57  
  *  <li> <code>String</code> values are turned into text nodes under the key
 58  
  *       element.
 59  
  *  <li> <code>Map</code> values are turned into elements recursively.
 60  
  *  <li> <code>List</code> and <code>Set</code> values are turned into repeated
 61  
  *       elements with the same key (and may be in turn either strings or maps).
 62  
  *  </ul>
 63  
  *  <p>
 64  
  *  Each of the conversion functions allows the caller to specify a list of
 65  
  *  keys as a filter. If these keys are present, only the specified keys will
 66  
  *  be processed from the input.
 67  
  *
 68  
  *  @since 1.1.3
 69  
  */
 70  0
 public class CollectionConverter
 71  
 {
 72  
     /**
 73  
      *  Creates a new DOM document from the passed map, without any namespace.
 74  
      *
 75  
      *   @param map         The source object. Its elements will be the children
 76  
      *                      of the document root.
 77  
      *   @param rootName    The local name given to the root element of the
 78  
      *                      generated document.
 79  
      *  @param  keyFilter   If present, the mappings will be limited to child
 80  
      *                      elements with the specified names.
 81  
      */
 82  
     public static Document convertToXml(
 83  
             Map<String,?> map, String rootName, String... keyFilter)
 84  
     {
 85  8
         Element root = DomUtil.newDocument(rootName);
 86  8
         appendElements(map, root, digestFilter(keyFilter));
 87  8
         return root.getOwnerDocument();
 88  
     }
 89  
 
 90  
 
 91  
     /**
 92  
      *  Creates a new DOM document from the passed map, in which all elements
 93  
      *  are members of the specified namespace and will inherit the root's
 94  
      *  prefix (if any).
 95  
      *
 96  
      *   @param map         The source object. Its elements will be the children
 97  
      *                      of the document root.
 98  
      *   @param rootName    The qualified name given to the root element of the
 99  
      *                      generated document (this is a <code>QName</code> to
 100  
      *                      avoid ambiguous argument lists).
 101  
      *  @param  keyFilter   If present, the mappings will be limited to child
 102  
      *                      elements with the specified names.
 103  
      */
 104  
     public static Document convertToXml(
 105  
             Map<String,?> map, QName rootName, String... keyFilter)
 106  
     {
 107  1
         Element root = DomUtil.newDocument(rootName);
 108  1
         appendElements(map, root, digestFilter(keyFilter));
 109  1
         return root.getOwnerDocument();
 110  
     }
 111  
 
 112  
 
 113  
     /**
 114  
      *  Converts a single XML element into a map.
 115  
      *
 116  
      *  @param  elem        The element.
 117  
      *  @param  keyFilter   If present, the mappings will be limited to child
 118  
      *                      elements with the specified names.
 119  
      */
 120  
     public static Map<String,?> convertToMap(Element elem, String... keyFilter)
 121  
     {
 122  8
         return convertToMap(elem, digestFilter(keyFilter));
 123  
     }
 124  
 
 125  
 
 126  
     /**
 127  
      *  Converts a list of XML elements into a list of maps.
 128  
      *
 129  
      *  @param  elems       The elements.
 130  
      *  @param  keyFilter   If present, the mappings will be limited to child
 131  
      *                      elements with the specified names.
 132  
      */
 133  
     public static List<Map<String,?>> convertToMap(List<Element> elems, String... keyFilter)
 134  
     {
 135  1
         List<Map<String,?>> result = new ArrayList<Map<String,?>>(elems.size());
 136  1
         Set<String> filter = digestFilter(keyFilter);
 137  1
         for (Element elem : elems)
 138  2
             result.add(convertToMap(elem, filter));
 139  1
         return result;
 140  
     }
 141  
 
 142  
 
 143  
 //----------------------------------------------------------------------------
 144  
 //  Internals
 145  
 //----------------------------------------------------------------------------
 146  
 
 147  
     /**
 148  
      *  Converts the varargs filter into  set, null if no filter provided.
 149  
      */
 150  
     private static Set<String> digestFilter(String... keyFilter)
 151  
     {
 152  18
         if (keyFilter.length == 0)
 153  14
             return null;
 154  
 
 155  4
         Set<String> filter = new HashSet<String>();
 156  11
         for (String key : keyFilter)
 157  7
             filter.add(key);
 158  4
         return filter;
 159  
     }
 160  
 
 161  
 
 162  
     /**
 163  
      *  Common conversion code for a single element, with digested filter.
 164  
      *
 165  
      *  @param  elem        The element.
 166  
      *  @param  keyFilter   If not <code>null</code> contains the child element
 167  
      *                      names that will be put in the map.
 168  
      */
 169  
     public static Map<String,?> convertToMap(Element elem, Set<String> keyFilter)
 170  
     {
 171  14
         Map<String,Object> result = new HashMap<String,Object>();
 172  14
         for (Element child : DomUtil.getChildren(elem))
 173  33
             appendChild(result, child, keyFilter);
 174  14
         return result;
 175  
     }
 176  
 
 177  
 
 178  
     private static void appendChild(Map<String,Object> map, Element child, Set<String> keyFilter)
 179  
     {
 180  33
         String key = DomUtil.getLocalName(child);
 181  33
         if ((keyFilter != null) && !keyFilter.contains(key))
 182  4
             return;
 183  
 
 184  29
         Object value = getChildValue(child, keyFilter);
 185  
 
 186  29
         if (!map.containsKey(key))
 187  
         {
 188  28
             map.put(key, value);
 189  28
             return;
 190  
         }
 191  
 
 192  1
         Object current = map.get(key);
 193  1
         if (current instanceof List)
 194  
         {
 195  0
             List<Object> list = (List<Object>)current;
 196  0
             list.add(value);
 197  0
         }
 198  
         else
 199  
         {
 200  1
             List<Object> list = new ArrayList<Object>();
 201  1
             list.add(current);
 202  1
             list.add(value);
 203  1
             map.put(key, list);
 204  
         }
 205  1
     }
 206  
 
 207  
 
 208  
     private static Object getChildValue(Element child, Set<String> keyFilter)
 209  
     {
 210  29
         if (DomUtil.hasElementChildren(child))
 211  4
             return convertToMap(child, keyFilter);
 212  
         else
 213  25
             return DomUtil.getText(child);
 214  
     }
 215  
 
 216  
 
 217  
     private static void appendElements(Map<String,?> map, Element parent, Set<String> keyFilter)
 218  
     {
 219  14
         for (Map.Entry<String,?> entry : map.entrySet())
 220  
         {
 221  29
             String key = entry.getKey();
 222  29
             if ((keyFilter != null) && !keyFilter.contains(key))
 223  3
                 continue;
 224  
 
 225  26
             Object value = entry.getValue();
 226  26
             appendElement(parent, key, value, keyFilter);
 227  26
         }
 228  14
     }
 229  
 
 230  
 
 231  
 
 232  
     private static void appendElement(Element parent, String key, Object value, Set<String> keyFilter)
 233  
     {
 234  34
         if (value == null)
 235  
         {
 236  1
             DomUtil.appendChildInheritNamespace(parent, key);
 237  
         }
 238  33
         else if (value instanceof String)
 239  
         {
 240  24
             Element child = DomUtil.appendChildInheritNamespace(parent, key);
 241  24
             DomUtil.setText(child, (String)value);
 242  24
         }
 243  9
         else if (value instanceof Map)
 244  
         {
 245  5
             Element child = DomUtil.appendChildInheritNamespace(parent, key);
 246  5
             appendElements((Map<String,?>)value, child, keyFilter);
 247  5
         }
 248  4
         else if (value instanceof Collection)
 249  
         {
 250  3
             for (Object obj : (Collection<?>)value)
 251  6
                 appendElement(parent, key, obj, keyFilter);
 252  
         }
 253  1
         else if (value.getClass().isArray())
 254  
         {
 255  3
             for (Object obj : (Object[])value)
 256  2
                 appendElement(parent, key, obj, keyFilter);
 257  
         }
 258  34
     }
 259  
 }