blob: 1ea61109de0326683bd83964522942c256b2aa87 [file] [log] [blame]
/*
* Copyright (c) 2007, Rickard Öberg. All Rights Reserved.
* Copyright (c) 2010, Niclas Hehdman. All Rights Reserved.
* Copyright (c) 2012, Paul Merlin. All Rights Reserved.
*
* 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.apache.zest.valueserialization.orgjson;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.apache.zest.api.injection.scope.Service;
import org.apache.zest.api.injection.scope.Structure;
import org.apache.zest.api.service.ServiceReference;
import org.apache.zest.api.structure.Application;
import org.apache.zest.api.structure.Module;
import org.apache.zest.api.value.ValueDeserializer;
import org.apache.zest.api.value.ValueSerializationException;
import org.apache.zest.functional.Function;
import org.apache.zest.spi.value.ValueDeserializerAdapter;
/**
* ValueDeserializer reading Values from JSON documents using org.json.
*/
public class OrgJsonValueDeserializer
extends ValueDeserializerAdapter<JSONTokener, Object>
{
public OrgJsonValueDeserializer(
@Structure Application application,
@Structure Module module,
@Service ServiceReference<ValueDeserializer> serviceRef )
{
super( application, module, serviceRef );
}
/* package */ OrgJsonValueDeserializer(
Application application,
Module module,
Function<Application, Module> valuesModuleFinder )
{
super( application, module, valuesModuleFinder );
}
@Override
protected JSONTokener adaptInput( InputStream input )
throws Exception
{
return new JSONTokener( new InputStreamReader( input, "UTF-8" ) );
}
@Override
protected Object readPlainValue( JSONTokener input )
throws Exception
{
Object nextValue = input.nextValue();
if( JSONObject.NULL.equals( nextValue ) )
{
return null;
}
else // Object or Array
if( JSONObject.class.isAssignableFrom( nextValue.getClass() )
|| JSONArray.class.isAssignableFrom( nextValue.getClass() ) )
{
throw new ValueSerializationException( "Asked for a Value but found an Object or an Array." );
}
return nextValue;
}
@Override
protected <T> Collection<T> readArrayInCollection( JSONTokener input,
Function<JSONTokener, T> deserializer,
Collection<T> collection )
throws Exception
{
char c = input.nextClean();
char q;
if( c == 'n' ) // null?
{
/*
* Handle unquoted text. This could be the values true, false, or
* null, or it can be a number. An implementation (such as this one)
* is allowed to also accept non-standard forms.
*
* Accumulate characters until we reach the end of the text or a
* formatting character.
*/
StringBuilder sb = new StringBuilder();
sb.setLength( 0 );
while( c >= ' ' && ",:]}/\\\"[{;=#".indexOf( c ) < 0 )
{
sb.append( c );
c = input.next();
}
input.back();
String s = sb.toString().trim();
if( !"null".equals( s ) )
{
input.syntaxError( "Unknown value: '" + s + "'" );
}
return null;
}
else if( c == '[' )
{
q = ']';
}
else
{
throw input.syntaxError( "A JSONArray text must start with '['" );
}
if( input.nextClean() == ']' )
{
return collection;
}
input.back();
for( ;; )
{
if( input.nextClean() == ',' )
{
input.back();
collection.add( null );
}
else
{
input.back();
collection.add( deserializer.map( input ) );
}
c = input.nextClean();
switch( c )
{
case ';':
case ',':
if( input.nextClean() == ']' )
{
return collection;
}
input.back();
break;
case ']':
case ')':
if( q != c )
{
throw input.syntaxError( "Expected a '" + Character.valueOf( q ) + "'" );
}
return collection;
default:
throw input.syntaxError( "Expected a ',' or ']'" );
}
}
}
@Override
protected <K, V> Map<K, V> readMapInMap( JSONTokener input,
Function<JSONTokener, K> keyDeserializer,
Function<JSONTokener, V> valueDeserializer,
Map<K, V> map )
throws Exception
{
char c = input.nextClean();
char q;
if( c == 'n' ) // null?
{
/*
* Handle unquoted text. This could be the values true, false, or
* null, or it can be a number. An implementation (such as this one)
* is allowed to also accept non-standard forms.
*
* Accumulate characters until we reach the end of the text or a
* formatting character.
*/
StringBuilder sb = new StringBuilder();
sb.setLength( 0 );
while( c >= ' ' && ",:]}/\\\"[{;=#".indexOf( c ) < 0 )
{
sb.append( c );
c = input.next();
}
input.back();
String s = sb.toString().trim();
if( !"null".equals( s ) )
{
input.syntaxError( "Unknown value: '" + s + "'" );
}
return null;
}
else if( c == '[' )
{
q = ']';
}
else
{
throw input.syntaxError( "A JSONArray text must start with '['" );
}
if( input.nextClean() == ']' )
{
return map;
}
input.back();
for( ;; )
{
if( input.nextClean() == ',' )
{
input.back();
}
else
{
input.back();
// Map entry!
if( input.nextClean() != '{' )
{
throw input.syntaxError( "A JSONObject text must begin with '{'" );
}
String objectKey;
K key = null;
V value = null;
boolean breakIteration = false;
while( !breakIteration )
{
c = input.nextClean();
switch( c )
{
case 0:
throw input.syntaxError( "A JSONObject text must end with '}'" );
case '}':
breakIteration = true;
continue;
default:
input.back();
objectKey = input.nextValue().toString();
}
/*
* The key is followed by ':'. We will also tolerate '=' or '=>'.
*/
c = input.nextClean();
if( c == '=' )
{
if( input.next() != '>' )
{
input.back();
}
}
else if( c != ':' )
{
throw input.syntaxError( "Expected a ':' after a key" );
}
if( "key".equals( objectKey ) )
{
key = keyDeserializer.map( input );
}
else if( "value".equals( objectKey ) )
{
value = valueDeserializer.map( input );
}
else
{
input.nextValue();
}
/*
* Pairs are separated by ','. We will also tolerate ';'.
*/
switch( input.nextClean() )
{
case ';':
case ',':
if( input.nextClean() == '}' )
{
breakIteration = true;
continue;
}
input.back();
continue;
case '}':
breakIteration = true;
continue;
default:
throw input.syntaxError( "Expected a ',' or '}'" );
}
}
if( key != null )
{
map.put( key, value );
}
}
c = input.nextClean();
switch( c )
{
case ';':
case ',':
if( input.nextClean() == ']' )
{
return map;
}
input.back();
break;
case ']':
case ')':
if( q != c )
{
throw input.syntaxError( "Expected a '" + Character.valueOf( q ) + "'" );
}
return map;
default:
throw input.syntaxError( "Expected a ',' or ']'" );
}
}
}
//
// Deserialization - Tree parsing
//
@Override
protected JSONObject readObjectTree( JSONTokener input )
throws Exception
{
Object objectTree = input.nextValue();
if( JSONObject.NULL.equals( objectTree ) )
{
return null;
}
return (JSONObject) objectTree;
}
@Override
protected Object asSimpleValue( Object inputNode )
throws Exception
{
if( JSONObject.NULL.equals( inputNode ) )
{
return null;
}
if( inputNode instanceof JSONObject || inputNode instanceof JSONArray )
{
throw new ValueSerializationException( "Expected a simple value but got " + inputNode );
}
return inputNode;
}
@Override
protected boolean isObjectValue( Object inputNode )
throws Exception
{
if( JSONObject.NULL.equals( inputNode ) )
{
return false;
}
return inputNode instanceof JSONObject;
}
@Override
protected boolean objectHasField( Object inputNode, String key )
throws Exception
{
if( JSONObject.NULL.equals( inputNode ) )
{
return false;
}
if( !( inputNode instanceof JSONObject ) )
{
throw new ValueSerializationException( "Expected an object but got " + inputNode );
}
JSONObject json = (JSONObject) inputNode;
return json.has( key );
}
@Override
protected <T> T getObjectFieldValue( Object inputNode, String key, Function<Object, T> valueDeserializer )
throws Exception
{
JSONObject json = (JSONObject) inputNode;
Object valueNode = json.opt( key );
if( JSONObject.NULL.equals( valueNode ) )
{
return null;
}
T value = valueDeserializer.map( valueNode );
return value;
}
@Override
protected <T> void putArrayNodeInCollection( Object inputNode, Function<Object, T> deserializer, Collection<T> collection )
throws Exception
{
if( JSONObject.NULL.equals( inputNode ) )
{
return;
}
if( !( inputNode instanceof JSONArray ) )
{
throw new ValueSerializationException( "Expected an array but got " + inputNode );
}
JSONArray array = (JSONArray) inputNode;
for( int idx = 0; idx < array.length(); idx++ )
{
Object item = array.get( idx );
T value = deserializer.map( item );
collection.add( value );
}
}
@Override
protected <K, V> void putArrayNodeInMap( Object inputNode, Function<Object, K> keyDeserializer, Function<Object, V> valueDeserializer, Map<K, V> map )
throws Exception
{
if( JSONObject.NULL.equals( inputNode ) )
{
return;
}
if( !( inputNode instanceof JSONArray ) )
{
throw new ValueSerializationException( "Expected an array but got " + inputNode );
}
JSONArray array = (JSONArray) inputNode;
for( int idx = 0; idx < array.length(); idx++ )
{
Object item = array.get( idx );
if( !( item instanceof JSONObject ) )
{
throw new ValueSerializationException( "Expected an object but got " + inputNode );
}
JSONObject object = (JSONObject) item;
Object keyNode = object.get( "key" );
Object valueNode = object.get( "value" );
K key = keyDeserializer.map( keyNode );
V value = valueDeserializer.map( valueNode );
if( key != null )
{
map.put( key, value );
}
}
}
@Override
protected <V> void putObjectNodeInMap( Object inputNode, Function<Object, V> valueDeserializer, Map<String, V> map )
throws Exception
{
if( JSONObject.NULL.equals( inputNode ) )
{
return;
}
if( !( inputNode instanceof JSONObject ) )
{
throw new ValueSerializationException( "Expected an object but got " + inputNode );
}
JSONObject object = (JSONObject) inputNode;
@SuppressWarnings( "unchecked" )
Iterator<String> it = object.keys();
while( it.hasNext() )
{
String key = it.next();
Object item = object.get( key );
V valueValue = valueDeserializer.map( item );
if( key != null )
{
map.put( key, valueValue );
}
}
}
}