Package org.freezedry.persistence.keyvalue

Source Code of org.freezedry.persistence.keyvalue.BasicKeyValueBuilder

/*
* 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.keyvalue;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.freezedry.persistence.containers.Pair;
import org.freezedry.persistence.keyvalue.renderers.PersistenceRenderer;
import org.freezedry.persistence.keyvalue.utils.KeyValueUtils;
import org.freezedry.persistence.tree.InfoNode;
import org.freezedry.persistence.utils.Constants;

/**
* Basic key-value list builder that flattens the semantic model and returns a list of key-value pairs.
* The entry point into the builder is the {@link #buildKeyValuePairs(InfoNode)} method.
* @author rob
*/
public class BasicKeyValueBuilder extends AbstractKeyValueBuilder {

  private static final Logger LOGGER = LoggerFactory.getLogger( BasicKeyValueBuilder.class );

  private boolean useClassAsRootKey = false;

  /**
   * Constructs a basic key-value builder that uses the specified renderers and separator.
   * @param renderers The mapping between the {@link Class} represented by an {@link InfoNode} and
   * the {@link PersistenceRenderer} used to create the key-value pair.
   * @param arrayRenderer The {@link PersistenceRenderer} used to create key-value pairs for
   * {@link InfoNode}s that represent an array.
   * @param separator The separator between the flattened elements of the key
   * @see AbstractKeyValueBuilder#getRenderer(Class)
   */
  public BasicKeyValueBuilder( final Map< Class< ? >, PersistenceRenderer > renderers,
                 final PersistenceRenderer arrayRenderer,
                 final String separator )
  {
    super( renderers, arrayRenderer, separator );
  }

  /**
   * Constructs a basic key-value builder that uses the default renderers and specified separator.
   * @param separator The separator between the flattened elements of the key
   */
  public BasicKeyValueBuilder( final String separator )
  {
    super( separator );
  }

  /**
   * Constructs a basic key-value builder that uses the default renderers and separator.
   */
  public BasicKeyValueBuilder()
  {
    super();
  }

  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.keyvalue.KeyValueBuilder#buildKeyValuePairs(org.freezedry.persistence.tree.InfoNode)
   */
  @Override
  public List< Pair< String, Object > > buildKeyValuePairs( final InfoNode rootInfoNode )
  {
    // create the map for holding the key-value pairs.
    final List< Pair< String, Object > > keyValuePairs = new ArrayList<>();

    // make a deep copy of the semantic model (since there are parts of the code that change the model)
    final InfoNode rootNode = rootInfoNode.getCopy();

    // recursively build the key-value pairs from the info-node tree
    buildKeyValuePairs( rootNode, rootNode.getPersistName(), keyValuePairs );
   
    // once complete, then return the list of key-value pairs
    return keyValuePairs;
  }

  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.keyvalue.KeyValueBuilder#buildKeyValuePairs(org.freezedry.persistence.tree.InfoNode, java.lang.String, java.util.List)
   */
  @Override
  public void buildKeyValuePairs( final InfoNode infoNode, final String key, final List< Pair< String, Object > > keyValues )
  {
    // run through the node's children, and for each one create and add the key-value pairs
    // to the list of key-value pairs
    for( InfoNode child : infoNode.getChildren() )
    {
      // if a child has been processed already, and marked processed, then we don't process
      // it again. this can occur if the node is, for example, a collection or map, in which
      // case the subnodes are processed outside of this loop, and this method may be called
      // recursively, and we want to ensure that the node is only processed once.
      if( !child.isProcessed() )
      {
        // create the new key value pairs
        createKeyValuePairs( child, key, keyValues, false );
       
        // mark the node as being processed
        child.setIsProcessed( true );
      }
    }
  }

  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.keyvalue.KeyValueBuilder#createKeyValuePairs(org.freezedry.persistence.tree.InfoNode, java.lang.String, java.util.List, boolean)
   */
  @Override
  public void createKeyValuePairs( final InfoNode infoNode, final String key, final List< Pair< String, Object > > keyValues, final boolean isWithholdPersitName )
  {
    // determine whether to show the persistence name. the isShowFullKey is top dog.
    final boolean isHidePersistName = (!isShowFullKey() && isWithholdPersitName);
   
    // grab the persistence renderer for the class or for its closest ancestor, or for the
    // array renderer if the class is an array
    final Class< ? > clazz = infoNode.getClazz();
    if( containsRenderer( clazz ) )
    {
      getRenderer( clazz ).buildKeyValuePair( infoNode, key, keyValues, isHidePersistName );
    }
    else if( clazz.isArray() )
    {
      getArrayRenderer().buildKeyValuePair( infoNode, key, keyValues, isHidePersistName );
    }
    else
    {
      // create the new key based on the specified key and the persistence name
      final String newKey = createKey( infoNode, key, isHidePersistName );
      final Pair< String, Object > keyValuePair = new Pair<>( newKey, null );
     
      // if the node is a leaf node, then it has a value, and we need to create a key-value pair
      // otherwise we need to recurse back to the calling method to build out the key-value pairs
      // for a compound node
      if( infoNode.isLeafNode() )
      {
        // create the key-value pair and add it to the list of key-values
        keyValuePair.setSecond( infoNode.getValue() );
        keyValues.add( keyValuePair );
      }
      else
      {
        buildKeyValuePairs( infoNode, newKey, keyValues );
      }
    }
  }
 
  /*
   * Creates a key based on the specified information. In particular, it deals with the suppression
   * of the leading separators when the specified key is null or empty. Also withholds the persistence name
   * from the key if it is intended to be withheld.
   * @param infoNode The current info node.
   * @param key The current key
   * @param isWithholdPersitName true if the persistence name is to be withheld from the key; false otherwise.
   * @return The newly minted key
   */
  private String createKey( final InfoNode infoNode, final String key, final boolean isWithholdPersitName )
  {
    final StringBuilder newKey = new StringBuilder();
   
    // if the key is empty then don't add anything
    if( key != null && !key.isEmpty() )
    {
      newKey.append( key );
      if( !isWithholdPersitName )
      {
        newKey.append( getSeparator() );
      }
    }
   
    // if we're not withholding the persistence name, then add it to the key
    if( !isWithholdPersitName )
    {
      newKey.append( infoNode.getPersistName() );
    }
   
    return newKey.toString();
  }
 
 
  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.keyvalue.KeyValueBuilder#buildInfoNode(java.lang.Class, java.util.List)
   */
  @Override
  public InfoNode buildInfoNode( final Class< ? > clazz, final List< Pair< String, String > > keyValues )
  {
    // grab the root key from all the values and use it to create the root info node.
    // recall that the root key should have the same name as the clazz we're using as
    // a template.
    final String rootKey = getRootKey( keyValues, clazz );
    final InfoNode rootNode = InfoNode.createRootNode( rootKey, clazz );
   
    // build the semantic model
    buildInfoNode( rootNode, keyValues );
   
    return rootNode;
  }

  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.keyvalue.KeyValueBuilder#buildInfoNode(org.freezedry.persistence.tree.InfoNode, java.util.List)
   */
  @Override
  public void buildInfoNode( final InfoNode parentNode, final List< Pair< String, String > > keyValues )
  {
    // grab the persistence name from the parent node. Validate that the first elements of each
    // key equal this the persistence name
    final String rootKey = parentNode.getPersistName();
    validiateRootKey( keyValues, getSeparator(), rootKey );
   
    // strip the root key element from all the keys. For example, suppose the keys all start with
    // "Division:". And suppose further that the rootKey = "Division". The "Division:" will be
    // stripped from each key in the list. So, "Division.people.Person[1]" would become "people.Person[1]".
    final List< Pair< String, String > > strippedKeyValues = KeyValueUtils.stripFirstKeyElement( keyValues, getSeparator() );

    // find the groups in the newly string list, and then create a new info node for each group
    final Map< String, List< Pair< String, String > > > groups = getGroups( strippedKeyValues, getSeparator() );
    for( Map.Entry< String, List< Pair< String, String > > > entry : groups.entrySet() )
    {
      // create the info node
      createInfoNode( parentNode, entry.getKey(), entry.getValue() );
    }
  }

  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.keyvalue.KeyValueBuilder#createInfoNode(org.freezedry.persistence.tree.InfoNode, java.lang.String, java.util.List)
   */
  @Override
  public void createInfoNode( final InfoNode parentNode, final String groupName, final List< Pair< String, String > > keyValues )
  {
    if( LOGGER.isDebugEnabled() )
    {
      final StringBuilder message = new StringBuilder( "Creating InfoNode; " )
          .append( "parent node: " ).append( parentNode.getPersistName() )
          .append( "group name: " ).append( groupName );
      for( Pair< String, String > pair : keyValues )
      {
        message.append( Constants.NEW_LINE ).append( pair );
      }
      LOGGER.debug( message.toString() );
    }

    // key-value list cannot be empty.
    if( keyValues.isEmpty() )
    {
      final StringBuilder message = new StringBuilder();
      message.append( "Cannot create an InfoNode when the specified list of key-values for the group is empty." ).append( Constants.NEW_LINE );
      message.append( "  Parent Node's Persistence Name: " ).append( parentNode.getPersistName() ).append( Constants.NEW_LINE );
      message.append( "  Group Name: " ).append( groupName ).append( Constants.NEW_LINE );
      LOGGER.error( message.toString() );
      throw new IllegalArgumentException( message.toString() );
    }
    // leaf node
    else if( keyValues.size() == 1 )
    {
      // grab the key and make sure that it matches the group name. at this point it shouldn't
      // have any decorations (i.e. for collection or map or anything else)
      final String name = keyValues.get( 0 ).getFirst();
      if( !groupName.equals( getGroupName( name ) ) )
      {
        final StringBuilder message = new StringBuilder();
        message.append( "The group name must match the persist name for a leaf node." ).append( Constants.NEW_LINE );
        message.append( "  Parent Node's Persistence Name: " ).append( parentNode.getPersistName() ).append( Constants.NEW_LINE );
        message.append( "  Group Name: " ).append( groupName ).append( Constants.NEW_LINE );
        message.append( "  Key: " ).append( name ).append( Constants.NEW_LINE );
        message.append( "  Value: " ).append( keyValues.get( 0 ).getSecond() );
        LOGGER.error( message.toString() );
        throw new IllegalStateException( message.toString() );
      }

      // in this case, the renderer for the key name should be a leaf-node renderer, so
      // lets pull it, and then asky it to build the info node for us, and add that new
      // info node to the parent, and then we're done
      getRenderer( name ).buildInfoNode( parentNode, keyValues );
    }
    // for compound nodes, we need to call the appropriate renderer's buildInfoNode(...) method. and
    // let the recursion begin.
    else
    {
      // grab the key from the first key value, and since all keys have the same group
      // all we need is to grab the first key and use it as a pattern for the remaining
      // key-value pairs in this group
      final String rootKey = keyValues.get( 0 ).getFirst();
     
      // get the appropriate renderer and ask it to build the info node from that type
      final PersistenceRenderer renderer = getRenderer( rootKey );
      if( renderer != null )
      {
        renderer.buildInfoNode( parentNode, keyValues );
      }
      else
      {
        final InfoNode node = InfoNode.createCompoundNode( null, groupName, null );
        parentNode.addChild( node );
        buildInfoNode( node, keyValues );
      }
    }
  }
   
  /**
   * In cases where the key-value pairs don't ALL begin with the SAME root key, this method
   * allows you to tell the reader to use the target class name as the root key. This will cause
   * the reader to act like all the keys begin with this key.
   * @param isUseClass true if the reader should use the target class name as the root key; false otherwise
   */
  public void setUseClassAsRootKey( final boolean isUseClass )
  {
    this.useClassAsRootKey = isUseClass;
  }

  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.keyvalue.KeyValueBuilder#getRootKey(java.util.List, java.lang.Class)
   */
  @Override
  public String getRootKey( final List< Pair< String, String > > keyValues, final Class< ? > clazz )
  {
    String key;
    if( useClassAsRootKey )
    {
      key = clazz.getSimpleName();
    }
    else
    {
      key = validiateRootKey( keyValues, getSeparator(), clazz.getSimpleName() );
    }
    return key;
  }

  /**
   * Pulls the first key element from each key, ensures that they are all the same, and then returns that root key.
   * @param keyValues The list of key-value pairs.
   * @param keyElementSeparator The separator between the key elements. The default value is found in the
   * {@link AbstractKeyValueBuilder#KEY_ELEMENT_SEPARATOR} and is usually "{@code :}".
   * @return the root key.
   */
  public String validiateRootKey( final List< Pair< String, String > > keyValues, final String keyElementSeparator )
  {
    return validiateRootKey( keyValues, keyElementSeparator, null );
  }
 
  /**
   * Pulls the first key element from each key, ensures that they are all the same, and then returns that root key.
   * @param keyValues The list of key-value pairs.
   * @param keyElementSeparator The separator between the key elements. The default value is found in the
   * {@link AbstractKeyValueBuilder#KEY_ELEMENT_SEPARATOR} and is usually "{@code :}".
   * @param desiredName The name the root key should end up being. If it isn't, this method throws an exception.
   * Set the desiredName to null if you don't want to validate against the name
   * @return the root key.
   * @see #validiateRootKey(List, String)
   */
  public String validiateRootKey( final List< Pair< String, String > > keyValues, final String keyElementSeparator, final String desiredName )
  {
    final Set< String > keySet = new LinkedHashSet<>();
    for( Pair< String, String > pair : keyValues )
    {
      keySet.add( getGroupName( KeyValueUtils.getFirstKeyElement( pair.getFirst(), keyElementSeparator ) ) );
    }
   
    // the first key must all be in the same group. that is the case if the set only
    // has one element, or if all the group names are the same
    if( keySet.size() != 1 )
    {
      final StringBuilder message = new StringBuilder();
      message.append( "The first element of all the keys must be the same. This is the root key." ).append( Constants.NEW_LINE );
      message.append( "  Set elements:" ).append( Constants.NEW_LINE );
      for( String keyName : keySet )
      {
        message.append( "    " ).append( keyName ).append( Constants.NEW_LINE );
      }
      LOGGER.error( message.toString() );
      throw new IllegalArgumentException( message.toString() );
    }
   
    // grab the key from the only element in the set
    final String rootKey = keySet.iterator().next();
    if( desiredName != null && !desiredName.equals( rootKey ) )
    {
      final StringBuilder message = new StringBuilder();
      message.append( "The root key is not the same as the persistence name" ).append( Constants.NEW_LINE );
      message.append( "  Root Key: " ).append( rootKey ).append( Constants.NEW_LINE );
      message.append( "  Persistence Name: " ).append( desiredName );
      LOGGER.error( message.toString() );
      throw new IllegalArgumentException( message.toString() );
    }
   
    return rootKey;
  }
 
  /*
   * Organizes the key values into groups based on the first key element of each key. Returns
   * a {@code Map< String, List< Pair< String, String > > >} where the map's key is the group
   * name. The group name is first key element from each key. The {@code List< Pair< String, String > >}
   * holds all the keys that start with the group name.
   * @param keyValues The list of key values
   * @param separator The key element separator
   * @return a {@code Map< String, List< Pair< String, String > > >} where the map's key is the group
   * name. The group name is first key element from each key. The {@code List< Pair< String, String > >}
   * holds all the keys that start with the group name.
   */
  private Map< String, List< Pair< String, String > > > getGroups( final List< Pair< String, String > > keyValues, final String separator )
  {
    final Map< String, List< Pair< String, String > > > groups = new LinkedHashMap<>();
    for( Pair< String, String > pair : keyValues )
    {
      // get the first key element.
      final String group = KeyValueUtils.getFirstKeyElement( pair.getFirst(), separator );
     
      // the thing is, though, that this could be decorated or formatted for a map or list
      // or something else. so we need to pull the group name off, and then we'll have to
      // figure out what the key represents, and how to deal with it. the good thing is that
      // the key name can only be part of [a-zA-Z_0-9], and so we can find the first character
      // that isn't that, and pull the key name out as the group
      final String groupName = getGroupName( group );
     
      // either add the pair to the list of key-value pairs of an existing group, or create
      // a new group that contains the key-value pair
      if( groups.containsKey( groupName ) )
      {
        groups.get( groupName ).add( new Pair<>( pair ) );
      }
      else
      {
        final List< Pair< String, String > > pairs = new ArrayList<>();
        pairs.add( new Pair<>( pair ) );
        groups.put( groupName, pairs );
      }
    }
   
    return groups;
  }

  /*
   * Returns the group name by finding the renderer for which the specified key
   * matches its regular expression, and then uses that renderer to parse the
   * group name. Returns null if no renderer was found.
   * @param key The key for which to parse the group name
   * @return the group name from the specified key, or null if no renderer claims it
   */
  private String getGroupName( final String key )
  {
    String group = null;
    final PersistenceRenderer renderer = getRenderer( key );
    if( renderer != null )
    {
      group = renderer.getGroupName( key );
    }
    return group;
  }
}
TOP

Related Classes of org.freezedry.persistence.keyvalue.BasicKeyValueBuilder

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.