Package org.freezedry.persistence.builders

Source Code of org.freezedry.persistence.builders.MapNodeBuilder

/*
* Copyright 2012 Robert Philipp
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/
package org.freezedry.persistence.builders;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.freezedry.persistence.PersistenceEngine;
import org.freezedry.persistence.annotations.PersistMap;
import org.freezedry.persistence.containers.Pair;
import org.freezedry.persistence.tree.InfoNode;
import org.freezedry.persistence.utils.Constants;
import org.freezedry.persistence.utils.ReflectionUtils;

/**
* Handles the persistence and serialization of {@link Map} objects. When use for serialization
* (i.e. typically when maps are root objects it is for serialization), then applies a key- and
* value-prefix to the key's and value's respectively.
* @author Robert Philipp
*/
public class MapNodeBuilder extends AbstractNodeBuilder {
 
  private static final Logger LOGGER = Logger.getLogger( MapNodeBuilder.class );
 
  // for maps that are root objects, these are the key and value prefixes so that upon reconstruction
  // there is a way to know which node contains the key, and which node contains the value
  private static final String KEY_PREFIX = "key";
  private static final String VALUE_PREFIX = "value";
  private static final String KEY_VALUE_SEPARATOR = ":";
 
  /**
   * Constructs the {@link NodeBuilder} for going between {@link Map}s and
   * {@link Object}s.
   * @param engine The {@link PersistenceEngine}
   */
  public MapNodeBuilder( PersistenceEngine engine )
  {
    super( engine, createDefaultConcreteClasses() );
  }

  /**
   * Constructs the {@link NodeBuilder} for going between {@link Map}s and
   * {@link Object}s.
   */
  public MapNodeBuilder()
  {
    super( createDefaultConcreteClasses() );
  }

  /**
   * Copy constructor
   * @param generator The {@link MapNodeBuilder} to copy
   * @see #getCopy()
   */
  public MapNodeBuilder( final MapNodeBuilder generator )
  {
    super( generator );
  }
 
  /*
   * @return the default mapping between the {@link Collection} interfaces and their concrete classes
   */
  private static Map< Class< ? >, Class< ? > > createDefaultConcreteClasses()
  {
    final Map< Class< ? >, Class< ? > > map = new HashMap<>();
    map.put( Map.class, LinkedHashMap.class );
    return map;
  }

  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.builders.infonodes.NodeBuilder#createInfoNode(java.lang.Class, java.lang.Object, java.lang.String)
   */
  @Override
  public InfoNode createInfoNode( final Class< ? > containingClass, final Object object, final String fieldName ) throws ReflectiveOperationException
  {
    // create the InfoNode object (we first have to determine the node type, down the road, we'll check the
    // factories for registered node generators for the Class< ? > of the object)
    final Class< ? > clazz = object.getClass();

    // create a compound node that holds the child nodes that form
    // the element of the List. For each child element, call this
    // method recursively to create the appropriate node.
    String persistName = null;
    if( containingClass != null )
    {
      try
      {
        final Field field = ReflectionUtils.getDeclaredField( containingClass, fieldName );
        persistName = ReflectionUtils.getPersistenceName( field );
      }
      catch( ReflectiveOperationException e )
      {
        final StringBuffer message = new StringBuffer();
        message.append( "Field not found in containing class:" + Constants.NEW_LINE );
        message.append( "  Containing class: " + containingClass.getName() + Constants.NEW_LINE );
        message.append( "  Field name: " + fieldName + Constants.NEW_LINE );
        LOGGER.info( message.toString() );
      }
    }
    if( persistName == null || persistName.isEmpty() )
    {
      persistName = fieldName;
    }
    final InfoNode node = InfoNode.createCompoundNode( fieldName, persistName, clazz );
   
    // set the entry, key, and value persistence names to the default value of the annotation. if the
    // map field is annotated, then we over write the default values with the annotated values.
    // does the class have a @PersistMap( keyPersistName = "xxxx", valuePersistName = "yyyy", entryPeristName = "zzzz" )
    String entryPersistName = PersistMap.ENTRY_PERSIST_NAME;
    String keyPersistName = PersistMap.KEY_PERSIST_NAME;
    String valuePersistName = PersistMap.VALUE_PERSIST_NAME;
    try
    {
      final Field field = ReflectionUtils.getDeclaredField( containingClass, fieldName );
      final PersistMap mapAnnotation = field.getAnnotation( PersistMap.class );
      if( mapAnnotation != null )
      {
        if( !mapAnnotation.entryPersistName().isEmpty() )
        {
          entryPersistName = mapAnnotation.entryPersistName();
        }
        if( !mapAnnotation.keyPersistName().isEmpty() )
        {
          keyPersistName = mapAnnotation.keyPersistName();
        }
        if( !mapAnnotation.valuePersistName().isEmpty() )
        {
          valuePersistName = mapAnnotation.valuePersistName();
        }
      }
    }
    catch( ReflectiveOperationException e )
    {
      final StringBuffer message = new StringBuffer();
      message.append( "Field not found in containing class:" + Constants.NEW_LINE );
      message.append( "  Containing class: " + containingClass.getName() + Constants.NEW_LINE );
      message.append( "  Field name: " + fieldName + Constants.NEW_LINE );
      LOGGER.info( message.toString() );
    }
   
    // run through the Map entries, recursively calling createNode(...) to create
    // the appropriate node which to add to the newly created compound node.
    for( Map.Entry< ?, ? > entry : ((Map< ?, ? >)object).entrySet() )
    {
      // create the map entry node
      final InfoNode entryNode = InfoNode.createCompoundNode( "", entryPersistName, entry.getClass() );
     
      // create the key node and add it to the entry node
      entryNode.addChild( createNode( clazz, entry.getKey(), keyPersistName ) );
     
      // create the value node and add it to the entry node
      entryNode.addChild( createNode( clazz, entry.getValue(), valuePersistName ) );
     
      // add the entry node to the info node representing the map
      node.addChild( entryNode );
    }
   
    return node;
  }

  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.builders.NodeBuilder#createInfoNode(java.lang.Object)
   */
  @Override
  public InfoNode createInfoNode( final Object object, final String persistName ) throws ReflectiveOperationException
  {
    // create the InfoNode object (we first have to determine the node type, down the road, we'll check the
    // factories for registered node generators for the Class< ? > of the object)
    final Class< ? > clazz = object.getClass();

    // create the root node
    final InfoNode node = InfoNode.createRootNode( clazz.getName(), clazz );

    // run through the Collection elements, recursively calling createNode(...) to create
    // the appropriate node which to add to the newly created compound node.
    // run through the Map entries, recursively calling createNode(...) to create
    // the appropriate node which to add to the newly created compound node.
    for( Map.Entry< ?, ? > entry : ((Map< ?, ? >)object).entrySet() )
    {
      // create the map entry node
      final InfoNode entryNode = InfoNode.createCompoundNode( "", entry.getClass().getSimpleName(), entry.getClass() );
     
      // create the key node and add it to the entry node
      entryNode.addChild( createNode( null, entry.getKey(), KEY_PREFIX + KEY_VALUE_SEPARATOR + entry.getKey().getClass().getName() ) );
     
      // create the value node and add it to the entry node
      entryNode.addChild( createNode( null, entry.getValue(), VALUE_PREFIX + KEY_VALUE_SEPARATOR + entry.getValue().getClass().getName() ) );
     
      // add the entry node to the info node representing the map
      node.addChild( entryNode );
    }
   
    return node;
  }
 
  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.builders.infonodes.NodeBuilder#createObject(java.lang.Class, org.freezedry.persistence.tree.nodes.InfoNode)
   */
  @Override
  public Object createObject( final Class< ? > containingClass, final Class< ? > clazz, final InfoNode node ) throws ReflectiveOperationException
  {
    // creates the map...
    final Map< ? super Object, ? super Object > map = createMap( clazz );

    // grab the generic type parameters from the info node, and make sure there is only one
    // (i.e. List< Double > should have java.lang.Double as the generic type) and pull out that type
    final List< Type > types = node.getGenericParameterTypes();
    if( types.size() != 2 )
    {
      final StringBuffer message = new StringBuffer();
      message.append( "Must have two generic parameter in map (one for the key and one for the value):" + Constants.NEW_LINE );
      message.append( "  Number of Generic Parameters: " + types.size() + Constants.NEW_LINE );
      message.append( "  Class name: " + clazz.getName() + Constants.NEW_LINE );
      message.append( "  Field name: " + node.getFieldName() + Constants.NEW_LINE );
      message.append( "  Persist name: " + node.getPersistName() + Constants.NEW_LINE );
      message.append( "  Generic Parameters: " + Constants.NEW_LINE );
      for( Type type : types )
      {
        message.append( "    " + ((Class< ? >)type).getName() + Constants.NEW_LINE );
      }
      throw new IllegalArgumentException( message.toString() );
    }
   
    // we need to get the generic types, but this isn't so simple. if the map is a simple map,
    // such as a Map< String, String >, then the keyClass will be a string and the valueClass
    // will by a string. But if either the key or the value, themselves, have a generic type,
    // such as a Map< String, Map< String, String > >, then we need to pull the generic type information
    // from the second map as well as the first. normally, we do reflection on the field to get the
    // generic type info, but for this inner map, we have no field, so we need to pull it, save it,
    // and then when we run through the nodes, we set it into the node so we have it on the next
    // recursive call....complicated, huh? so, the keyTypes will hold any type generic information of
    // the key. the keyClass holds the Class of the key. note that the ParameterizedType means that
    // it has generic type information.
    final Pair< Class< ? >, List< Type > > keyInfo = extractTypeInfo( types.get( 0 ) );
    final Class< ? > keyClass = keyInfo.getFirst();
    final List< Type > keyTypes = keyInfo.getSecond();
   
    // same as for the key
    final Pair< Class< ? >, List< Type > > valueInfo = extractTypeInfo( types.get( 1 ) );
    final Class< ? > valueClass = valueInfo.getFirst();
    final List< Type > valueTypes = valueInfo.getSecond();
       
    // grab the names that the semantic model uses to represent the key and value.
    final Pair< String, String > keyValueNames = getKeyValueNames( containingClass, node );
    String keyPersistenceName = keyValueNames.getFirst();
    String valuePersistenceName = keyValueNames.getSecond();
   
    // run through the nodes, calling the persistence engine to create the element objects
    // and add them to the newly created map. each info node should have an entry node, and
    // each entry node should have a key node and a value node.
    for( InfoNode entryNode : node.getChildren() )
    {
      final List< InfoNode > keyValue = entryNode.getChildren();
      if( keyValue.size() != 2 )
      {
        final StringBuffer message = new StringBuffer();
        message.append( "The info node for this map must have two nodes. But snap! It doesn't" + Constants.NEW_LINE );
        message.append( "  Number of nodes: " + keyValue.size() + Constants.NEW_LINE );
        message.append( "  Node names: " + Constants.NEW_LINE );
        for( InfoNode childNode : keyValue )
        {
          message.append( "    " + childNode.getPersistName() + Constants.NEW_LINE );
        }
        LOGGER.error( message.toString() );
        throw new IllegalArgumentException( message.toString() );
      }
     
      // the order of the elements can be reversed. For example, the key could be the second
      // element (instead of the first) and the value could be the first. We check both possibilities
      Object key = null;
      Object value = null;
      final InfoNode firstNode = keyValue.get( 0 );
      final InfoNode secondNode = keyValue.get( 1 );
      if( keyPersistenceName.equals( firstNode.getPersistName() ) &&
        valuePersistenceName.equals( secondNode.getPersistName() )  )
      {
        key = buildObject( containingClass, keyClass, keyTypes, firstNode, node );
        value = buildObject( containingClass, valueClass, valueTypes, secondNode, node );
      }
      else if( keyPersistenceName.equals( secondNode.getPersistName() ) &&
           valuePersistenceName.equals( firstNode.getPersistName() ) )
      {
        key = buildObject( containingClass, keyClass, keyTypes, secondNode, node );
        value = buildObject( containingClass, valueClass, valueTypes, firstNode, node );
      }
     
      // add the new objects to the map
      map.put( key, value );
    }
   
    // return the newly created and populated collection
    return map;
  }
 
  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.builders.NodeBuilder#createObject(java.lang.Class, org.freezedry.persistence.tree.InfoNode)
   */
  @Override
  public Object createObject( final Class< ? > clazz, final InfoNode node ) throws ReflectiveOperationException
  {
    // creates the map...
    final Map< ? super Object, ? super Object > map = createMap( clazz );

    // run through the nodes, calling the persistence engine to create the element objects
    // and add them to the newly created map. each info node should have an entry node, and
    // each entry node should have a key node and a value node.
    for( InfoNode entryNode : node.getChildren() )
    {
      final List< InfoNode > keyValue = entryNode.getChildren();
      if( keyValue.size() != 2 )
      {
        final StringBuffer message = new StringBuffer();
        message.append( "The info node for this map must have two nodes. But snap! It doesn't" + Constants.NEW_LINE );
        message.append( "  Number of nodes: " + keyValue.size() + Constants.NEW_LINE );
        message.append( "  Node names: " + Constants.NEW_LINE );
        for( InfoNode childNode : keyValue )
        {
          message.append( "    " + childNode.getPersistName() + Constants.NEW_LINE );
        }
        LOGGER.error( message.toString() );
        throw new IllegalArgumentException( message.toString() );
      }
     
      // the order of the elements can be reversed. For example, the key could be the second
      // element (instead of the first) and the value could be the first. We check both possibilities
      final InfoNode firstNode = keyValue.get( 0 );
      final InfoNode secondNode = keyValue.get( 1 );
      Pair< Object, Object > keyValuePair = null;
      if( firstNode.getPersistName().startsWith( KEY_PREFIX + KEY_VALUE_SEPARATOR ) &&
        secondNode.getPersistName().startsWith( VALUE_PREFIX + KEY_VALUE_SEPARATOR ) )
      {
        keyValuePair = getKeyValuePair( firstNode, secondNode, node );
      }
      else if( firstNode.getPersistName().startsWith( VALUE_PREFIX + KEY_VALUE_SEPARATOR ) &&
           secondNode.getPersistName().startsWith( KEY_PREFIX + KEY_VALUE_SEPARATOR ) )
      {
        keyValuePair = getKeyValuePair( secondNode, firstNode, node );
      }

      // add the new objects to the map
      map.put( keyValuePair.getFirst(), keyValuePair.getSecond() );
    }
   
    // return the newly created and populated collection
    return map;
  }
 
  /**
   * Creates a key-value pair containing the object built from the key node and value node. 
   * @param keyNode The node containing the key
   * @param valueNode The node containing the value
   * @param parentNode The parent node that contains both the key and value nodes
   * @return A key-value pair containing the object built from the key node and value node.
   * @throws ReflectiveOperationException
   */
  private Pair< Object, Object > getKeyValuePair( final InfoNode keyNode, final InfoNode valueNode, final InfoNode parentNode ) throws ReflectiveOperationException
  {
    final Class< ? > keyClass = Class.forName( keyNode.getPersistName().split( "\\" + KEY_VALUE_SEPARATOR )[ 1 ] );
    final List< Type > keyTypes = Arrays.asList( (Type)keyClass );
    final Object key = buildObject( null, keyClass, keyTypes, keyNode, parentNode );

    final Class< ? > valueClass = Class.forName( valueNode.getPersistName().split( "\\" + KEY_VALUE_SEPARATOR )[ 1 ] );
    final List< Type > valueTypes = Arrays.asList( (Type)valueClass );
    final Object value = buildObject( null, valueClass, valueTypes, valueNode, parentNode );
   
    return new Pair< Object, Object >( key, value );
  }
 
  /*
   * Instantiates a {@link Map} object based on the specified {@link Class}. However, if the specified {@link Class}
   * is an interface, then it used the default concrete {@link Map} class found in {@link #concreteMapClass}.
   * @param clazz The {@link Class} from which to build the {@link Map}
   * @return The newly constructed {@link Map}
   * @see #setDefaultMapClass(Class)
   * @throws InstantiationException
   * @throws IllegalAccessException
   */
  private Map< ? super Object, ? super Object > createMap( final Class< ? > clazz ) throws InstantiationException, IllegalAccessException
  {
    // make sure the class isn't an interface, and if it is, then use the default concrete map class.
    Class< ? > classType = clazz;
    if( containsInterface( clazz ) )
    {
      classType = getClassForInterface( clazz );
    }
   
    // instantiate
    @SuppressWarnings( "unchecked" )
    final Map< ? super Object, ? super Object > map = Map.class.cast( classType.newInstance() );
   
    // done...
    return map;
  }
 
  /**
   * Returns the key and value names for the {@link Map}. These are either default values, or, if
   * the field has a {@link PersistMap} annotation, it may have overridden either the key or value name.
   * For example, suppose the user has annotated the field with
   * {@code @PersistMap(entryPersistName="client",keyPersistName="endPoint",valuePersistName="weight")}.
   * Then the info node of the children would contain "endpoint" instead of the expected "Key" for the
   * key name, and "weight" instead of the expected "Value" for the value. And the we need to know that
   * so we can deal with it.
   * @param containingClass The {@link Class} containing the field represented by the specified node
   * @param node The node representing the field
   * @return The key and value names associated with the {@link Map}
   */
  private Pair< String, String > getKeyValueNames( final Class< ? > containingClass, final InfoNode node )
  {
    // create the pair containing the default key and value names
    final Pair< String, String > keyValue = new Pair< String, String >( PersistMap.KEY_PERSIST_NAME, PersistMap.VALUE_PERSIST_NAME );
   
    // attempt to grab the field with the node's persistence name (in this case this would be the
    // field name)
    final String persistName = node.getPersistName();
    Field field = null;
    try
    {
      field = ReflectionUtils.getDeclaredField( containingClass, persistName );
    }
    catch( NoSuchFieldException e )
    {
      field = ReflectionUtils.getFieldForPersistenceName( containingClass, node.getPersistName() );
    }
   
    // if a field was found, then see if that field has an annotation, and if that annotation overrides
    // the key name and/or the value name
    if( field != null )
    {
      final PersistMap mapAnnotation = field.getAnnotation( PersistMap.class );
      if( mapAnnotation != null )
      {
        final String keyName = mapAnnotation.keyPersistName();
        if( keyName != null && !keyName.isEmpty() )
        {
          keyValue.setFirst( keyName );
        }
       
        final String valueName = mapAnnotation.valuePersistName();
        if( valueName != null && !valueName.isEmpty() )
        {
          keyValue.setSecond( valueName );
        }
      }
    }
   
    return keyValue;
  }
 
  /*
   * (non-Javadoc)
   * @see com.synapse.copyable.Copyable#getCopy()
   */
  @Override
  public NodeBuilder getCopy()
  {
    return new MapNodeBuilder( this );
  }

}
TOP

Related Classes of org.freezedry.persistence.builders.MapNodeBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.