
com.antiaction.common.json.JSONObjectMappings Maven / Gradle / Ivy
/*
* JSON library.
* Copyright 2012-2013 Antiaction (http://antiaction.com/)
*
* 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 com.antiaction.common.json;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import com.antiaction.common.json.annotation.JSON;
import com.antiaction.common.json.annotation.JSONConverter;
import com.antiaction.common.json.annotation.JSONIgnore;
import com.antiaction.common.json.annotation.JSONName;
import com.antiaction.common.json.annotation.JSONNullValues;
import com.antiaction.common.json.annotation.JSONNullable;
import com.antiaction.common.json.annotation.JSONTypeInstance;
import com.antiaction.common.json.representation.JSONStructureMarshaller;
import com.antiaction.common.json.representation.JSONStructureUnmarshaller;
import com.antiaction.common.json.representation.JSONTextMarshaller;
import com.antiaction.common.json.representation.JSONTextUnmarshaller;
/**
* TODO javadoc
* @author Nicholas
* Created on 13/11/2012
*/
public class JSONObjectMappings {
protected final Class>[] zeroArgsParameterTypes = new Class[ 0 ];
public final Map classMappings = new TreeMap();
protected final Map converterNameIdMap = new TreeMap();
protected int converterIds = 0;
public Map> overrideIgnoreMapSet = new HashMap>();
public Map> forceNullableMapSet = new HashMap>();
protected final JSONTextMarshaller textMarshaller;
protected final JSONTextUnmarshaller textUnmarshaller;
protected final JSONStructureMarshaller structureMarshaller;
protected final JSONStructureUnmarshaller structureUnmarshaller;
protected final JSONStreamMarshaller streamMarshaller;
protected final JSONStreamUnmarshaller streamUnmarshaller;
public JSONObjectMappings() {
textMarshaller = new JSONTextMarshaller();
textUnmarshaller = new JSONTextUnmarshaller();
structureMarshaller = new JSONStructureMarshaller( this );
structureUnmarshaller = new JSONStructureUnmarshaller( this );
streamMarshaller = new JSONStreamMarshaller( this );
streamUnmarshaller = new JSONStreamUnmarshaller( this );
}
public JSONTextMarshaller getTextMarshaller() {
return textMarshaller;
}
public JSONTextUnmarshaller getTextUnmarshaller() {
return textUnmarshaller;
}
public JSONStructureMarshaller getStructureMarshaller() {
return structureMarshaller;
}
public JSONStructureUnmarshaller getStructureUnmarshaller() {
return structureUnmarshaller;
}
public JSONStreamMarshaller getStreamMarshaller() {
return streamMarshaller;
}
public JSONStreamUnmarshaller getStreamUnmarshaller() {
return streamUnmarshaller;
}
public int getConverterNameId(String converterName) throws JSONException {
Integer id = converterNameIdMap.get( converterName );
if ( id == null ) {
throw new JSONException( "Unknown conveter name: " + converterName );
}
return id;
}
public int getConverters() {
return converterIds;
}
public JSONObjectMapping register(Class> clazz) throws JSONException {
JSONObjectMapping objectMapping = classMappings.get( clazz.getName() );
if ( objectMapping == null ) {
int classTypeMask = ClassTypeModifiers.getClassTypeModifiersMask( clazz );
if ( (classTypeMask & JSONObjectMappingConstants.CLASS_INVALID_TYPE_MODIFIERS_MASK) != 0 ) {
throw new JSONException( "Unsupported class type." );
}
classTypeMask &= JSONObjectMappingConstants.CLASS_VALID_TYPE_MODIFIERS_MASK;
if ( (classTypeMask == JSONObjectMappingConstants.VALID_CLASS) || (classTypeMask == JSONObjectMappingConstants.VALID_MEMBER_CLASS) ) {
objectMapping = mapClass( clazz );
}
else if ( classTypeMask == JSONObjectMappingConstants.VALID_ARRAY_CLASS ) {
objectMapping = mapArray( clazz );
}
else {
throw new JSONException( "Unsupported class type." );
}
}
return objectMapping;
}
protected JSONObjectMapping mapArray(Class> clazz) throws JSONException {
int level;
Class> fieldType = null;
JSONObjectMapping fieldObjectMapping = null;
JSONObjectMapping objectMapping = JSONObjectMapping.getArrayMapping();
classMappings.put( clazz.getName(), objectMapping );
try {
String arrayTypeName = clazz.getName();
// debug
//System.out.println( typeName );
Integer arrayType = JSONObjectMappingConstants.arrayPrimitiveTypeMappings.get( arrayTypeName );
// debug
//System.out.println( arrayType );
if ( arrayType == null ) {
level = 0;
while ( level < arrayTypeName.length() && arrayTypeName.charAt( level ) == '[' ) {
++level;
}
// [L;
if ( level == 1 && level < arrayTypeName.length() && arrayTypeName.charAt( level ) == 'L' && arrayTypeName.endsWith( ";" ) ) {
arrayType = JSONObjectMappingConstants.T_OBJECT;
arrayTypeName = arrayTypeName.substring( level + 1, arrayTypeName.length() - 1 );
fieldType = Class.forName( arrayTypeName, true, clazz.getClassLoader() );
// Cache
fieldObjectMapping = classMappings.get( arrayTypeName );
if ( fieldObjectMapping == null ) {
fieldObjectMapping = mapClass( fieldType );
}
}
else {
throw new JSONException( "Unsupported array type '" + arrayTypeName + "'." );
}
}
objectMapping.arrayType = arrayType;
objectMapping.className = arrayTypeName;
objectMapping.clazz = fieldType;
objectMapping.objectMapping = fieldObjectMapping;
// TODO FieldMapping need to work for top level arrays.
objectMapping.fieldMapping = new JSONObjectFieldMapping();
// Try to support Collection field type. Caused regression error.
objectMapping.fieldMapping.type = JSONObjectMappingConstants.T_ARRAY;
objectMapping.fieldMapping.arrayType = arrayType;
objectMapping.fieldMapping.className = arrayTypeName;
objectMapping.fieldMapping.clazz = fieldType;
} catch (ClassNotFoundException e) {
new JSONException( e );
}
return objectMapping;
}
protected JSONObjectMapping mapClass(Class> clazz) throws JSONException {
JSONObjectFieldMapping json_fm;
JSON json;
JSONIgnore ignore;
JSONObjectMapping objectMapping = JSONObjectMapping.getObjectMapping();
classMappings.put( clazz.getName(), objectMapping );
// Try to set to support List in stream unmarshall.
objectMapping.className = clazz.getName();
objectMapping.clazz = clazz;
Constructor> constructor = null;
try {
constructor = clazz.getConstructor( zeroArgsParameterTypes );
}
catch (NoSuchMethodException e) {
}
if ( constructor == null ) {
throw new JSONException( clazz.getName() + " does not have a zero argument constructor!" );
}
json = clazz.getAnnotation( JSON.class );
if ( json != null ) {
String[] ignores = json.ignore();
for ( int i=0; i fieldType = null;
String fieldTypeName;
Integer type;
Integer arrayType = 0;
int fieldModsMask = 0;
int classTypeMask = 0;
JSONObjectMapping fieldObjectMapping;
Integer[] parametrizedObjectTypes;
JSONObjectMapping[] parametrizedObjectMappings;
int level;
JSONNullable nullable;
boolean bNullable;
JSONNullValues nullValues;
boolean bNullValues;
JSONConverter converter;
JSONName jsonName;
JSONTypeInstance jsonTypeInstance;
Class> fieldTypeInstance;
Boolean bInterfaceInstance;
try {
for ( int i=0; i overrideFieldIgnoreSet = overrideIgnoreMapSet.get( objectMapping.className );
if ( bIgnore ) {
if ( overrideFieldIgnoreSet != null && overrideFieldIgnoreSet.contains( field.getName() ) ) {
bIgnore = false;
}
}
if ( !bIgnore ) {
fieldModsMask = ClassTypeModifiers.getFieldModifiersMask( field );
// debug
//System.out.println( field.getName() + " - " + ClassTypeModifiers.toString( fieldModsMask ) );
bIgnore = (fieldModsMask & JSONObjectMappingConstants.FIELD_IGNORE_TYPE_MODIFIER) != 0;
}
if ( !bIgnore ) {
fieldType = field.getType();
fieldTypeName = fieldType.getName();
classTypeMask = ClassTypeModifiers.getClassTypeModifiersMask( fieldType );
// debug
//System.out.println( fieldTypeName + " " + ClassTypeModifiers.toString( classTypeMask ) );
type = JSONObjectMappingConstants.primitiveTypeMappings.get( fieldTypeName );
fieldObjectMapping = null;
parametrizedObjectTypes = null;
parametrizedObjectMappings = null;
fieldTypeInstance = null;
bInterfaceInstance = false;
if ( type == null ) {
jsonTypeInstance = field.getAnnotation( JSONTypeInstance.class );
if ( jsonTypeInstance != null ) {
fieldTypeInstance = jsonTypeInstance.value();
if ( fieldTypeInstance == null ) {
throw new JSONException( "JSONTypeInstance annotation with null value is not allowed." );
}
int typeInstanceMask = ClassTypeModifiers.getClassTypeModifiersMask( fieldTypeInstance );
if ( (typeInstanceMask & JSONObjectMappingConstants.FIELD_INVALID_TYPE_MODIFIERS_MASK) != 0 ) {
throw new JSONException( "Unsupported field instance type modifier(s) [" + ClassTypeModifiers.toString( typeInstanceMask ) + "] for class: " + fieldTypeInstance.getName() );
}
}
if ( (classTypeMask & JSONObjectMappingConstants.FIELD_INVALID_TYPE_MODIFIERS_MASK) != 0 ) {
if ( (classTypeMask & ClassTypeModifiers.CT_INTERFACE) == 0 ) {
// debug
//System.out.println( "Unsupported field modifier(s) [" + ClassTypeModifiers.toString( classTypeMask ) + "] for class: " + fieldTypeName );
throw new JSONException( "Unsupported field modifier(s) [" + ClassTypeModifiers.toString( classTypeMask ) + "] for class: " + fieldTypeName );
}
else {
// Check the interface itself.
int colType = ClassTypeModifiers.getCollectionInterfaceType( fieldType );
if ( colType == ClassTypeModifiers.COLTYPE_OTHER ) {
colType = ClassTypeModifiers.getCollectionType( fieldType );
}
if ( colType != ClassTypeModifiers.COLTYPE_OTHER ) {
if ( fieldTypeInstance == null ) {
throw new JSONException( "[" + objectMapping.className + "] Missing @JSONTypeInstance annotation on collection interface field of type: " + fieldTypeName );
}
int instanceColType = ClassTypeModifiers.getCollectionType( fieldTypeInstance );
if ( colType != instanceColType ) {
throw new JSONException( "Field interface type(" + ClassTypeModifiers.colTypeToString( colType ) + ") and instance type(" + ClassTypeModifiers.colTypeToString( instanceColType ) + ") are not compatible." );
}
bInterfaceInstance = true;
}
/*
switch ( colType ) {
case ClassTypeModifiers.COLTYPE_LIST:
throw new JSONException( "Unsupported collection interface field type. (" + fieldTypeName + ")" );
case ClassTypeModifiers.COLTYPE_MAP:
throw new JSONException( "Unsupported collection interface field type. (" + fieldTypeName + ")" );
case ClassTypeModifiers.COLTYPE_SET:
throw new JSONException( "Unsupported collection interface field type. (" + fieldTypeName + ")" );
default:
// Check extended interfaces.
colType = ClassTypeModifiers.getCollectionType( fieldType );
switch ( colType ) {
case ClassTypeModifiers.COLTYPE_LIST:
throw new JSONException( "Unsupported collection interface field type. (" + List.class.getName() + " .. " + fieldTypeName + ")" );
case ClassTypeModifiers.COLTYPE_MAP:
throw new JSONException( "Unsupported collection interface field type. (" + Map.class.getName() + " .. " + fieldTypeName + ")" );
case ClassTypeModifiers.COLTYPE_SET:
throw new JSONException( "Unsupported collection interface field type. (" + Set.class.getName() + " .. " + fieldTypeName + ")" );
default:
throw new JSONException( "Unsupported field class type." );
}
}
*/
}
}
classTypeMask &= JSONObjectMappingConstants.FIELD_VALID_TYPE_MODIFIERS_MASK;
if ( (classTypeMask == JSONObjectMappingConstants.VALID_CLASS) || (classTypeMask == JSONObjectMappingConstants.VALID_MEMBER_CLASS || bInterfaceInstance) ) {
Type genericType = field.getGenericType();
// debug - uncomment
//System.out.println( "GT: " + genericType + " * " + genericType.getClass() );
if ( genericType instanceof Class ) {
int colType = ClassTypeModifiers.getCollectionType( (Class>)genericType );
switch ( colType ) {
case ClassTypeModifiers.COLTYPE_LIST:
case ClassTypeModifiers.COLTYPE_MAP:
case ClassTypeModifiers.COLTYPE_SET:
throw new JSONException( "Collection must have parametrized type(s). (" + fieldTypeName + ")" );
default:
type = JSONObjectMappingConstants.T_OBJECT;
// Cache
fieldObjectMapping = classMappings.get( fieldTypeName );
if ( fieldObjectMapping == null ) {
fieldObjectMapping = mapClass( Class.forName( fieldTypeName, true, clazz.getClassLoader() ) );
}
break;
}
}
else if ( genericType instanceof ParameterizedType ) {
ParameterizedType parameterizedType = (ParameterizedType)genericType;
Type[] typeArguments = parameterizedType.getActualTypeArguments();
boolean bWildCardUsed = false;
for ( Type typeArgument : typeArguments ) {
if ( typeArgument instanceof WildcardType ) {
bWildCardUsed = true;
}
}
if ( bWildCardUsed ) {
throw new JSONException( "Unsupported use of wildcard parameterized types." );
}
parametrizedObjectTypes = new Integer[ typeArguments.length ];
parametrizedObjectMappings = new JSONObjectMapping[ typeArguments.length ];
for ( int j=0; j parameterizedTypeClass = ((Class>)typeArgument);
// debug - uncomment
//System.out.println( parameterizedTypeClass );
parametrizedObjectTypes[ j ] = JSONObjectMappingConstants.primitiveTypeMappings.get( parameterizedTypeClass.getName() );
if ( parametrizedObjectTypes[ j ] == null ) {
parametrizedObjectTypes[ j ] = JSONObjectMappingConstants.T_OBJECT;
parametrizedObjectMappings[ j ] = classMappings.get( parameterizedTypeClass.getName() );
if ( parametrizedObjectMappings[ j ] == null ) {
parametrizedObjectMappings[ j ] = mapClass( parameterizedTypeClass );
}
}
}
int colType = ClassTypeModifiers.getCollectionType( (Class>)parameterizedType.getRawType() );
switch ( colType ) {
case ClassTypeModifiers.COLTYPE_LIST:
type = JSONObjectMappingConstants.T_LIST;
break;
case ClassTypeModifiers.COLTYPE_MAP:
type = JSONObjectMappingConstants.T_MAP;
break;
case ClassTypeModifiers.COLTYPE_SET:
type = JSONObjectMappingConstants.T_SET;
break;
default:
throw new JSONException( "Unsupported generic class. " + field.getClass() );
}
}
else if ( genericType instanceof TypeVariable ) {
throw new JSONException( "Unable to determine type of generic field '" + field.getName() + "'" );
}
}
else if ( classTypeMask == JSONObjectMappingConstants.VALID_ARRAY_CLASS ) {
type = JSONObjectMappingConstants.T_ARRAY;
arrayType = JSONObjectMappingConstants.arrayPrimitiveTypeMappings.get( fieldTypeName );
if ( arrayType == null ) {
level = 0;
while ( level < fieldTypeName.length() && fieldTypeName.charAt( level ) == '[' ) {
++level;
}
// [L;
if ( level < fieldTypeName.length() ) {
if ( level == 1 ) {
if ( fieldTypeName.charAt( level ) == 'L' && fieldTypeName.endsWith( ";" ) ) {
arrayType = JSONObjectMappingConstants.T_OBJECT;
fieldTypeName = fieldTypeName.substring( level + 1, fieldTypeName.length() - 1 );
fieldType = Class.forName( fieldTypeName, true, clazz.getClassLoader() );
// Cache
fieldObjectMapping = classMappings.get( fieldTypeName );
if ( fieldObjectMapping == null ) {
fieldObjectMapping = mapClass( fieldType );
}
}
else {
throw new JSONException( "Unsupported array type '" + fieldTypeName + "'." );
}
}
else {
throw new JSONException( "Unsupported multi-dimensional array type '" + fieldTypeName + "'." );
}
}
else {
throw new JSONException( "Invalid array type '" + fieldTypeName + "'." );
}
}
}
else {
throw new JSONException( "Unsupported field class type." );
}
}
// debug
//System.out.println( type );
if ( type != null ) {
if ( fieldTypeInstance == null ) {
fieldTypeInstance = fieldType;
}
json_fm = new JSONObjectFieldMapping();
json_fm.fieldName = field.getName();
json_fm.type = type;
json_fm.arrayType = arrayType;
json_fm.className = fieldTypeName;
json_fm.clazz = fieldType;
json_fm.instanceClazz = fieldTypeInstance;
json_fm.objectMapping = fieldObjectMapping;
json_fm.parametrizedObjectTypes = parametrizedObjectTypes;
json_fm.parametrizedObjectMappings = parametrizedObjectMappings;
json_fm.field = clazz.getDeclaredField( json_fm.fieldName );
json_fm.field.setAccessible( true );
objectMapping.fieldMapping = json_fm;
bNullable = objectMapping.nullableSet.contains( json_fm.fieldName );
if ( !bNullable ) {
nullable = field.getAnnotation( JSONNullable.class );
if ( nullable != null ) {
bNullable = nullable.value();
}
}
Set forceNullableSet = forceNullableMapSet.get( objectMapping.className );
if ( !bNullable ) {
if ( forceNullableSet != null && forceNullableSet.contains( field.getName() ) ) {
bNullable = true;
}
}
if ( bNullable ) {
if ( json_fm.type < JSONObjectMappingConstants.T_OBJECT ) {
throw new JSONException( "Primitive types can not be nullable." );
}
json_fm.nullable = true;
}
bNullValues = objectMapping.nullValuesSet.contains( json_fm.fieldName );
if ( !bNullValues) {
nullValues = field.getAnnotation( JSONNullValues.class );
if ( nullValues != null ) {
bNullValues = nullValues.value();
}
}
if ( bNullValues ) {
if ( json_fm.type >= JSONObjectMappingConstants.T_ARRAY && json_fm.arrayType < JSONObjectMappingConstants.T_OBJECT ) {
throw new JSONException( "Array of primitive type can not have null values." );
}
json_fm.nullValues = true;
}
converter = field.getAnnotation( JSONConverter.class );
if ( converter != null ) {
json_fm.converterName = converter.name();
Integer converterId = converterNameIdMap.get( json_fm.converterName );
if ( converterId == null ) {
converterId = converterIds++;
converterNameIdMap.put( json_fm.converterName, converterId );
}
json_fm.converterId = converterId;
objectMapping.converters = true;
}
jsonName = field.getAnnotation( JSONName.class );
if ( jsonName != null ) {
json_fm.jsonName = jsonName.value();
}
else {
json_fm.jsonName = json_fm.fieldName;
}
objectMapping.fieldMappingsMap.put( json_fm.jsonName, json_fm );
objectMapping.fieldMappingsList.add( json_fm );
}
}
}
objectMapping.fieldMappingsArr = objectMapping.fieldMappingsList.toArray( new JSONObjectFieldMapping[ 0 ] );
}
catch (ClassNotFoundException e) {
throw new JSONException( e );
}
catch (NoSuchFieldException e) {
throw new JSONException( e );
}
catch (SecurityException e) {
throw new JSONException( e );
}
return objectMapping;
}
public String toString() {
StringBuilder sb = new StringBuilder();
Iterator> iter = classMappings.entrySet().iterator();
Entry entry;
JSONObjectMapping objectMapping;
while ( iter.hasNext() ) {
entry = iter.next();
sb.append( "class: " );
sb.append( entry.getKey() );
sb.append( "\n" );
objectMapping = entry.getValue();
objectMapping.toString( sb );
}
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy