Package org.freezedry.persistence.readers

Source Code of org.freezedry.persistence.readers.JsonReader

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

import org.freezedry.persistence.PersistenceEngine;
import org.freezedry.persistence.tree.InfoNode;
import org.freezedry.persistence.utils.Constants;
import org.freezedry.persistence.utils.JsonUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;

/**
* Reads the JSON string from the specifed {@link InputStream} and converts it to the semantic model.
* JSON has the following format (taken from <a href="http://json.org/">json.org</a>):<p>
* <pre>
* object
*    {}
*    { members }
*    
* members
*    pair
*    pair , members
*   
* pair
*    string : value
*   
* array
*    []
*    [ elements ]
*   
* elements
*    value
*    value , elements
*   
* value
*    string
*    number
*    object
*    array
*    true
*    false
*    null
* </pre>
* @see PersistenceReader
* @see XmlReader
*
* @author rob
*/
public class JsonReader implements PersistenceReader {

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

  /*
   * (non-Javadoc)
   * @see org.freezedry.persistence.readers.PersistenceReader#read(java.lang.Class, java.io.InputStream)
   */
  @Override
  public InfoNode read( final Class< ? > clazz, final Reader input )
  {
    // load the json string from the input stream into a json object
    final JSONObject jsonObject = createRootJsonObject( clazz, input );
     
    // the first/top of the json string must be the class name, so there should only be one key,
    // and one value associated with that key { "root_key" : { members } }, where members is
    // defined by { pair, members } and pair is defined by "key", "value" (see class documentation).
    // we still have to add the value as a node.
    final InfoNode rootNode = createRootNode( jsonObject, clazz );
   
    // grab the value and deal with one of the three possibilities:
    // 1. the value is a json object and therefore has name-value pairs
    // 2. the value is a json array and therefore has elements
    // 3. the value is neither, and therefore is a simple value
    // In the first two cases, we build out the remainder of the nodes recursively. In
    // the third case, we set in the value and class in root node and we're done.
    final Object value = JsonUtils.getValue( jsonObject, rootNode.getPersistName() );
    if( value instanceof JSONObject )
    {
      buildInfoNode( (JSONObject)value, rootNode );
    }
    else if( value instanceof JSONArray )
    {
      buildInfoNode( (JSONArray)value, clazz.getSimpleName(), rootNode );
    }
    else
    {
      final StringBuffer message = new StringBuffer();
      message.append( "Invalid JSON representation of a class. Root node must have at least one named element." + Constants.NEW_LINE );
      message.append( "  JSON String: " + Constants.NEW_LINE );
      try{ message.append( jsonObject.toString( 2 ) ); } catch( JSONException e ) {}
      LOGGER.error( message.toString() );
      throw new IllegalArgumentException( message.toString() );
    }
    return rootNode;
  }
 
  /*
   * Creates the {@link InfoNode} for each of the key-value pairs found in {@link JSONObject} and
   * adds them to the specified parent {@link InfoNode}, recursively.
   * @param jsonObject The {@link JSONObject} from which to create the new {@link InfoNode}s and add
   * them to the specified parent {@link InfoNode}.
   * @param infoNode The parent {@link InfoNode} to which to add the new {@link InfoNode}s.
   * @see #buildInfoNode(JSONArray, String, InfoNode)
   */
  private static void buildInfoNode( final JSONObject jsonObject, final InfoNode infoNode )
  {
    // grab a list of the element names held in the json object, and then grab the
    // json object for each name
    final List< String > names = getJsonNames( jsonObject );
    for( String name : names )
    {
      final Object object = JsonUtils.getValue( jsonObject, name );
     
      // build the info node and add it to its parent
      final InfoNode newInfoNode = createInfoNode( name, object, infoNode );
      if( newInfoNode != null )
      {
        infoNode.addChild( newInfoNode );
      }
    }
  }

  /*
   * Creates the {@link InfoNode} for each element in the {@link JSONArray} and adds it to the specified
   * parent {@link InfoNode} recursively.
   * @param jsonArray The {@link JSONArray} containing the elements from which to create {@link InfoNode}s and
   * add them to the parent {@link InfoNode}.
   * @param nodeName The name to assign to the new nodes. This is the name of the list in the JSON document.
   * @param infoNode The parent {@link InfoNode} to which to add the new {@link InfoNode}s.
   * @see #buildInfoNode(JSONObject, InfoNode)
   */
  private static void buildInfoNode( final JSONArray jsonArray, final String nodeName, final InfoNode infoNode )
  {
    // run through the elements of the json array (recursively) building info nodes
    for( int i = 0; i < jsonArray.length(); ++i )
    {
      final Object object = JsonUtils.getElement( jsonArray, i );
     
      // build the info node and add it to its parent info node
      final InfoNode newInfoNode = createInfoNode( nodeName, object, infoNode );
      infoNode.addChild( newInfoNode );
    }
  }

  /*
   * Part of the recursive algorithm that creates {@link InfoNode}s. If the specified {@code value} represents a leaf
   * node, then we create it and return. However, if the {@code value} represents a compound node, such as a {@link JSONObject}
   * or a {@link JSONArray}, then we recurse, calling the appropriate {@code buildInfoNode(...)} method. These methods will
   * in turn call back to this method. It all ends when the specified {@code value} is a leaf node.
   * @param nodeName The persistence name to assign to the new {@link InfoNode}
   * @param value The {@code value} of the node which could be a {@link JSONObject}, a {@link JSONArray}, or a plain object
   * which represents a leaf node.
   * @param parentNode The parent {@link InfoNode} to which the new node would be added. This is only used when the
   * {@code value} is a {@link JSONArray}, in which case the elements are added to this parent node and no new node
   * is created.
   * @return The newly created {@link InfoNode}
   * @see #buildInfoNode(JSONObject, InfoNode)
   * @see #buildInfoNode(JSONArray, String, InfoNode)
   */
  private static InfoNode createInfoNode( final String nodeName, final Object value, final InfoNode parentNode )
  {
    // there are three possibilities:
    // 1. the value is a json object, in which case the info node is compound
    // 2. the value is a json array, in which case the info node is compound
    // 3. the value is one of the remaining types (boolean, string, int, double, long, etc)
    //    in which case the info node is a leaf
    InfoNode node = null;
    if( value instanceof JSONObject )
    {
      node = InfoNode.createCompoundNode( null, nodeName, null );
      buildInfoNode( (JSONObject)value, node );
    }
    else if( value instanceof JSONArray )
    {
      buildInfoNode( (JSONArray)value, nodeName, parentNode );
    }
    else
    {
      node = InfoNode.createLeafNode( null, value, nodeName, null );
    }
    return node;
  }
 
  /*
   * Returns a list of names from the {@link JSONArray} of element names
   * @param names the {@link JSONArray} of element names
   * @return The list of element names
   */
  private static List< String > getJsonNames( final JSONObject jsonObject )
  {
    // grab the names of the elements
    final JSONArray names = jsonObject.names();
   
    // create the list of names
    final List< String > jsonNames = new ArrayList<>();
    for( int i = 0; i < names.length(); ++i )
    {
      try
      {
        jsonNames.add( names.getString( i ) );
      }
      catch( JSONException e )
      {
        final StringBuffer message = new StringBuffer();
        message.append( "Unable to get the key name from the JSON array." + Constants.NEW_LINE );
        message.append( "  Number Elements: " + names.length() + Constants.NEW_LINE );
        message.append( "  Names: " + Constants.NEW_LINE );
        for( int j = 0; j < names.length(); ++j )
        {
          try
          {
            message.append( "    " + names.getString( j ) + Constants.NEW_LINE );
          }
          catch( JSONException e2 )
          {
            message.append( "    ** missed name **" + Constants.NEW_LINE );
          }
        }
        LOGGER.error( message.toString() );
        throw new IllegalArgumentException( message.toString() );
      }
    }
    return jsonNames;
  }
 
  /*
   * Reads the source JSON string from the {@link InputStream} and create the root JSON object
   * @param clazz the {@link Class} to reconstitute
   * @param input The {@link InputStream} from which to read the JSON source
   * @return A string representation of the JSON source
   */
  private static JSONObject createRootJsonObject( final Class< ? > clazz, final Reader input )
  {
    // parse the source json string into a json object
    String source = null;
    JSONObject jsonObject = null;
    try( final Scanner scanner = new Scanner( input ) )
    {
      source = scanner.useDelimiter( "\\A" ).next();
   
      jsonObject = new JSONObject( source );
    }
    catch( NoSuchElementException e )
    {
      final StringBuffer message = new StringBuffer();
      message.append( "Unable to load the JSON from the input stream" + Constants.NEW_LINE );
      message.append( "  Class Name: " + clazz.getName() + Constants.NEW_LINE );
      LOGGER.error( message.toString() );
      throw new IllegalArgumentException( message.toString(), e );
    }
    catch( JSONException e )
    {
      final StringBuffer message = new StringBuffer();
      message.append( "Unable to parse JSON string into a JSON object for further processing" + Constants.NEW_LINE );
      message.append( "  Class Name: " + clazz.getName() + Constants.NEW_LINE );
      message.append( "  Source JSON String: " + Constants.NEW_LINE );
      message.append( source );
      LOGGER.error( message.toString() );
      throw new IllegalArgumentException( message.toString(), e );
    }
 
    return jsonObject;
  }

  /*
   * Creates the root {@link InfoNode} from the root element of the JSON object
   * @param jsonObject The {@link JSONObject} to be converted into the semantic model
   * @param clazz The {@link Class} of the object to ultimately build.
   * @return The root {@link InfoNode}
   */
  private static InfoNode createRootNode( final JSONObject jsonObject, final Class< ? > clazz )
  {
    // the first/top of the json string must be the class name, so there should only be one key
    final JSONArray names = jsonObject.names();
    if( names.length() != 1 )
    {
      final StringBuffer message = new StringBuffer();
      message.append( "Root element of the JSON string must have exactly one element representing" + Constants.NEW_LINE );
      message.append( "the class into which the semantic model will be converted." + Constants.NEW_LINE );
      message.append( "  Class Name: " + clazz.getName() + Constants.NEW_LINE );
      message.append( "  Number Elements: " + names.length() + Constants.NEW_LINE );
      message.append( "  Names: " + Constants.NEW_LINE );
      for( int i = 0; i < names.length(); ++i )
      {
        try
        {
          message.append( "    " + names.getString( i ) + Constants.NEW_LINE );
        }
        catch( JSONException e )
        {
          message.append( "    ** missed name **" + Constants.NEW_LINE );
        }
      }
      message.append( "  Source JSON Object: " + Constants.NEW_LINE );
      message.append( jsonObject.toString() );
      LOGGER.error( message.toString() );
      throw new IllegalArgumentException( message.toString() );
    }
   
    // grab the root name, now that we've confirmed only one element
    String rootName = null;
    try
    {
      rootName = names.getString( 0 );
    }
    catch( JSONException e )
    {
      final StringBuffer message = new StringBuffer();
      message.append( "Couldn't get the root name from the JSON object." + Constants.NEW_LINE );
      message.append( jsonObject.toString() );
      LOGGER.error( message.toString() );
      throw new IllegalArgumentException( message.toString() );
    }
   
    // create and return the root info node
    return InfoNode.createRootNode( rootName, clazz );
  }
}
TOP

Related Classes of org.freezedry.persistence.readers.JsonReader

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.