Coverage Report - org.apache.shindig.social.core.util.xstream.InterfaceClassMapper
 
Classes in this File Line Coverage Branch Coverage Complexity
InterfaceClassMapper
82%
56/68
70%
31/44
0
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements. See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership. The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License. You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied. See the License for the
 16  
  * specific language governing permissions and limitations under the License.
 17  
  */
 18  
 package org.apache.shindig.social.core.util.xstream;
 19  
 
 20  
 import com.google.common.collect.Maps;
 21  
 
 22  
 import com.google.inject.ImplementedBy;
 23  
 
 24  
 import com.thoughtworks.xstream.mapper.Mapper;
 25  
 import com.thoughtworks.xstream.mapper.MapperWrapper;
 26  
 
 27  
 import org.apache.shindig.social.opensocial.model.Person;
 28  
 
 29  
 import org.apache.commons.logging.Log;
 30  
 import org.apache.commons.logging.LogFactory;
 31  
 
 32  
 import java.util.Collection;
 33  
 import java.util.HashMap;
 34  
 import java.util.List;
 35  
 import java.util.Map;
 36  
 
 37  
 /**
 38  
  * The InterfaceClassMapper provides the central mapping of the XStream bean
 39  
  * converter. It is used by XStream to determine the element names and classes
 40  
  * being mapped. This is driven by a number of read only data structures that
 41  
  * are injected on creation. The resolution of classes follow the inheritance
 42  
  * model. To map all collections to an element we would use the Collection.class
 43  
  * as the reference class and then ArrayList and Set would both be mapped to the
 44  
  * same element.
 45  
  */
 46  
 
 47  
 public class InterfaceClassMapper extends MapperWrapper {
 48  
 
 49  
   /**
 50  
    * A logger.
 51  
    */
 52  1
   private static final Log log = LogFactory.getLog(InterfaceClassMapper.class);
 53  
   /**
 54  
    * A map of element names to classes.
 55  
    */
 56  222
   private Map<String, Class<?>> elementClassMap = Maps.newHashMap();
 57  
   /**
 58  
    * The first child of the root object. If the root object is not a collection,
 59  
    * this is null. If the root object is a collection and all the elements are
 60  
    * the same this is set to the class of those elements. Once the first call
 61  
    * has been made to the mapper, this is set back to null. Note its a thread
 62  
    * local enabling this class to remain multi threaded.
 63  
    */
 64  222
   private ThreadLocal<Class<?>> firstChild = new ThreadLocal<Class<?>>();
 65  
   /**
 66  
    * A map of classed to ommit, the key is the field name, and the value is an
 67  
    * array of classes where that field is ommitted from the output.
 68  
    */
 69  
   private Map<String, Class<?>[]> omitMap;
 70  
   /**
 71  
    * A map of elements, where the ClassMapping object defines how the classes
 72  
    * are mapped to elements.
 73  
    */
 74  
   private List<ClassFieldMapping> elementMappingList;
 75  
   /**
 76  
    * A map of parent elements used where there is a collection as the root
 77  
    * object being serialized. This ensures that the root obejct gets the right
 78  
    * element name rather than list a generic &gt;list&lt;
 79  
    */
 80  
   private List<ClassFieldMapping> listElementMappingList;
 81  
   /**
 82  
    * An implementation of a tracking stack for the writer. If this class is to
 83  
    * be thread safe, the implementation of this field must also be thread safe
 84  
    * as it is shared over multiple threads.
 85  
    */
 86  
   private WriterStack writerStack;
 87  
   
 88  
   /**
 89  
    * A list of explicit mapping specifications. 
 90  
    */
 91  
   private List<ImplicitCollectionFieldMapping> itemFieldMappings;
 92  
 
 93  
   /**
 94  
    * Create an Interface Class Mapper with a configuration.
 95  
    *
 96  
    * @param writerStack
 97  
    *          A thread safe WriterStack implementation connected to the XStream
 98  
    *          driver and hence all the writers.
 99  
    * @param wrapped
 100  
    *          the base mapper to be wrapped by this wrapper. All mappers must
 101  
    *          wrap the default mapper.
 102  
    * @param elementMappingList
 103  
    *          A list of element to class mappings specified by ClassFieldMapping
 104  
    *          Object. This list is priority ordered with the highest priority
 105  
    *          mappings coming first.
 106  
    * @param listElementMappingList
 107  
    *          A list of element names to use as the base element where there is
 108  
    *          a collection of the same type objects being serialized.
 109  
    * @param omitMap
 110  
    *          A map of fields in classes to omit from serialization.
 111  
    * @param elementClassMap
 112  
    *          a map of element names to class types.
 113  
    */
 114  
   public InterfaceClassMapper(WriterStack writerStack, Mapper wrapped,
 115  
       List<ClassFieldMapping> elementMappingList,
 116  
       List<ClassFieldMapping> listElementMappingList,
 117  
       List<ImplicitCollectionFieldMapping> itemFieldMappings,
 118  
       Map<String, Class<?>[]> omitMap, Map<String, Class<?>> elementClassMap) {
 119  222
     super(wrapped);
 120  222
     this.elementClassMap = elementClassMap;
 121  222
     this.elementMappingList = elementMappingList;
 122  222
     this.listElementMappingList = listElementMappingList;
 123  222
     this.omitMap = omitMap;
 124  222
     this.writerStack = writerStack;
 125  222
     this.itemFieldMappings = itemFieldMappings;
 126  222
   }
 127  
 
 128  
   /**
 129  
    * Set the base object at the start of a serialization, this ensures that the
 130  
    * base element type is appropriate for the elements that are contained within
 131  
    * the object. This method only has any effect if the base object is a
 132  
    * Collection of some form. other wise this method has no effect on the state.
 133  
    * The method is thread safe attaching state to the thread for later
 134  
    * retrieval.
 135  
    *
 136  
    * @param base
 137  
    *          the base object being serialized.
 138  
    */
 139  
   public void setBaseObject(Object base) {
 140  37
     firstChild.set(null);
 141  37
     if (Collection.class.isAssignableFrom(base.getClass())) {
 142  3
       Collection<?> c = (Collection<?>) base;
 143  3
       Class<?> clazz = null;
 144  
 
 145  3
       for (Object o : c) {
 146  6
         if (clazz == null) {
 147  2
           clazz = o.getClass();
 148  2
         } else {
 149  4
           if (!clazz.equals(o.getClass())) {
 150  0
             clazz = null;
 151  0
             break;
 152  
           }
 153  
         }
 154  6
       }
 155  3
       firstChild.set(clazz);
 156  3
       if (log.isDebugEnabled()) {
 157  0
         log.debug("First Child set to " + clazz);
 158  
       }
 159  
     }
 160  37
   }
 161  
 
 162  
   /**
 163  
    * <p>
 164  
    * Get the serialized element name for a specific class. If this is the first
 165  
    * object to be serialized, and it is a collection, then the elements of the
 166  
    * collection will have been inspected to determine if the container should
 167  
    * have a special name. These names are specified in the
 168  
    * listElementMappingList which is specified on construction. If the first
 169  
    * element is not found a standard list.container element name is used to
 170  
    * contain all the others, this list type is only ever used in the unit tests.
 171  
    * </p>
 172  
    * <p>
 173  
    * For subsequent elements, the class is mapped directly to a element name at
 174  
    * the same level, specified via the elementMappingList which is injected in
 175  
    * the constructor. This mapping looks to see if the class in question
 176  
    * inherits of extends the classes in the list and uses the element name
 177  
    * associated wit the first match.
 178  
    * </p>
 179  
    *
 180  
    * @see com.thoughtworks.xstream.mapper.MapperWrapper#serializedClass(java.lang.Class)
 181  
    * @param type
 182  
    *          the type of the class to the serialized
 183  
    * @return the name of the element that that should be used to contain the
 184  
    *         class when serialized.
 185  
    */
 186  
   @SuppressWarnings("unchecked")
 187  
   // the API is not generic
 188  
   @Override
 189  
   public String serializedClass(Class type) {
 190  101
     String parentElementName = writerStack.peek();
 191  101
     if (Collection.class.isAssignableFrom(type) && firstChild.get() != null) {
 192  
       // empty list, if this is the first one, then we need to look at the
 193  
       // first child setup on startup.
 194  2
       if (log.isDebugEnabled()) {
 195  0
         log.debug("Converting Child to " + firstChild.get());
 196  
       }
 197  2
       type = firstChild.get();
 198  2
       firstChild.set(null);
 199  2
       if (log.isDebugEnabled()) {
 200  0
         log.debug("serializedClass(" + type + ") is a collection member "
 201  
             + Collection.class.isAssignableFrom(type));
 202  
       }
 203  2
       for (ClassFieldMapping cfm : listElementMappingList) {
 204  0
         if (cfm.matches(parentElementName, type)) {
 205  0
           return cfm.getElementName();
 206  
         }
 207  0
       }
 208  2
       return "list.container";
 209  
     } else {
 210  
       // but after we have been asked once, then clear
 211  99
       firstChild.set(null);
 212  99
       if (log.isDebugEnabled()) {
 213  0
         log.debug("serializedClass(" + type + ")");
 214  
       }
 215  99
       for (ClassFieldMapping cfm : elementMappingList) {
 216  1175
         if (cfm.matches(parentElementName, type)) {
 217  67
           if (log.isDebugEnabled()) {
 218  0
             log.debug("From MAP serializedClass(" + type + ")  =="
 219  
                 + cfm.getElementName());
 220  
           }
 221  67
           return cfm.getElementName();
 222  
         }
 223  1108
       }
 224  
 
 225  
     }
 226  
 
 227  32
     String fieldName = super.serializedClass(type);
 228  32
     if (log.isDebugEnabled()) {
 229  0
       log.debug("--- From Super serializedClass(" + type + ")  ==" + fieldName);
 230  
     }
 231  32
     return fieldName;
 232  
 
 233  
   }
 234  
 
 235  
   /**
 236  
    * Checks to see if the field in a class should be serialized. This is
 237  
    * controlled buy the omitMap Map which is keyed by the field name. Each entry
 238  
    * in the map contains a list of classes where the field name should be
 239  
    * excluded from the output.
 240  
    *
 241  
    * @param definedIn
 242  
    *          the class the field is defined in
 243  
    * @param fieldName
 244  
    *          the field being considered
 245  
    * @return true of the field should be serialized false if it should be
 246  
    *         ignored.
 247  
    * @see com.thoughtworks.xstream.mapper.MapperWrapper#shouldSerializeMember(java.lang.Class,
 248  
    *      java.lang.String)
 249  
    *
 250  
    */
 251  
   @SuppressWarnings("unchecked")
 252  
   // API is not generic
 253  
   @Override
 254  
   public boolean shouldSerializeMember(Class definedIn, String fieldName) {
 255  1047
     Class<?>[] omitList = omitMap.get(fieldName);
 256  1047
     if (omitList != null) {
 257  16
       for (Class<?> omit : omitList) {
 258  16
         if (omit.isAssignableFrom(definedIn)) {
 259  16
           return false;
 260  
         }
 261  
       }
 262  
     }
 263  1031
     return super.shouldSerializeMember(definedIn, fieldName);
 264  
   }
 265  
 
 266  
   /**
 267  
    * Get the real class associated with an element name from the
 268  
    * elementMappingList.
 269  
    *
 270  
    * @param elementName
 271  
    *          the name of the element being read.
 272  
    * @see com.thoughtworks.xstream.mapper.MapperWrapper#realClass(java.lang.String)
 273  
    */
 274  
   @SuppressWarnings("unchecked")
 275  
   @Override
 276  
   public Class realClass(String elementName) {
 277  5
     Class<?> clazz = elementClassMap.get(elementName);
 278  5
     if (clazz == null) {
 279  0
       clazz = super.realClass(elementName);
 280  
     }
 281  5
     return clazz;
 282  
   }
 283  
 
 284  
 
 285  
   
 286  
   /**
 287  
    * {@inheritDoc}
 288  
    * @see com.thoughtworks.xstream.mapper.MapperWrapper#getImplicitCollectionDefForFieldName(java.lang.Class, java.lang.String)
 289  
    */
 290  
   @Override
 291  
   public ImplicitCollectionMapping getImplicitCollectionDefForFieldName(
 292  
       Class itemType, String fieldName) {
 293  345
     for ( ImplicitCollectionFieldMapping ifm : itemFieldMappings) {
 294  8411
       if ( ifm.matches(itemType, fieldName) ) {
 295  48
         return ifm;
 296  
       }
 297  8363
     }
 298  297
     return super.getImplicitCollectionDefForFieldName(itemType, fieldName);
 299  
   }
 300  
 
 301  
 
 302  
   
 303  
 }