/* * Copyright © 2008, 2012 Pedro Agulló Soliveres. * * This file is part of DirectJNgine. * * DirectJNgine is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License. * * Commercial use is permitted to the extent that the code/component(s) * do NOT become part of another Open Source or Commercially developed * licensed development library or toolkit without explicit permission. * * DirectJNgine is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with DirectJNgine. If not, see <http://www.gnu.org/licenses/>. * * This software uses the ExtJs library (http://extjs.com), which is * distributed under the GPL v3 license (see http://extjs.com/license). */ package com.softwarementors.extjs.djn.gson; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import java.util.Stack; import com.softwarementors.extjs.djn.ClassUtils; import com.softwarementors.extjs.djn.CollectionUtils; import com.softwarementors.extjs.djn.StringUtils; import edu.umd.cs.findbugs.annotations.NonNull; public class JsonDeserializationManager { private static @NonNull ThreadLocal<JsonDeserializationManager> manager = new ThreadLocal<JsonDeserializationManager>(); private static @NonNull Set<Class<?>> manyValuedClasses = new HashSet<Class<?>>(); static { manyValuedClasses.add( Collection.class ); } public static void registerManyValuedClasses( Class<?> clazz, Class<?>[] otherClasses) { assert clazz != null; assert otherClasses != null; manyValuedClasses.add(clazz); Collections.addAll( manyValuedClasses, otherClasses ); } JsonDeserializationManager() { // Avoid instantiation } private @NonNull Stack<Object> parents = new Stack<Object>(); private @NonNull Stack<String> fields = new Stack<String>(); private @NonNull Map<Object,Set<String>> fieldExclusions = new IdentityHashMap<Object,Set<String>>(); private @NonNull Set<String> rootExclusions = new HashSet<String>(); private @NonNull Set<String> rootInclusions = new HashSet<String>(); private Object root; private boolean excludeManyValuedFields; public static JsonDeserializationManager getManager() { if( manager.get() == null ) { manager.set(new JsonDeserializationManager() ); } return manager.get(); } public void friendOnlyAccess_pushField(String name) { assert !StringUtils.isEmpty(name); this.fields.push(name); } public void friendOnlyAccess_popField() { this.fields.pop(); } public void friendOnlyAccess_pushParent(Object obj) { assert obj != null; this.parents.push(obj); } public void friendOnlyAccess_popParent() { this.parents.pop(); } public void excludeManyValuedFields() { this.excludeManyValuedFields = true; } public void excludeFieldPaths(String firstFieldPath, String... fieldPaths) { assert this.rootExclusions != null; assert firstFieldPath != null; assert fieldPaths != null; this.rootInclusions.remove(firstFieldPath); CollectionUtils.removeAll( this.rootInclusions, fieldPaths ); this.rootExclusions.add( firstFieldPath ); Collections.addAll(this.rootExclusions, fieldPaths); } public void includeFieldPaths(String firstFieldPath, String... fieldPaths) { assert this.rootInclusions != null; assert firstFieldPath != null; assert fieldPaths != null; this.rootExclusions.remove(firstFieldPath); CollectionUtils.removeAll( this.rootExclusions, fieldPaths ); this.rootInclusions.add( firstFieldPath ); Collections.addAll(this.rootInclusions, fieldPaths); } /* public void excludeObjectFieldPaths(Object t, String firstFieldPath, String... fieldPaths) { if( t == null ) { return; } assert firstFieldPath != null; assert fieldPaths != null; Set<String> exclusions = this.fieldExclusions.get(t); if( exclusions == null ) { exclusions = new HashSet<String>(); this.fieldExclusions.put( t, exclusions ); } exclusions.add(firstFieldPath); Collections.addAll( exclusions, fieldPaths); } */ public boolean friendOnlyAccess_isFieldExcluded(Object value, String field) { assert value != null; assert field != null; if( isRootManyValuedFieldExcluded(value, field)) { return true; } if( this.fieldExclusions.isEmpty()) { return false; } // value is an special case: can't call hasExclusionForField // because it checks the parents list. // However, check is trivial, because if it has field, it // must be as is, not as a dotted path! if( this.fieldExclusions.containsKey(value)) { Set<String> ex = this.fieldExclusions.get(value); if( ex.contains(field)) { return true; } } for( Object obj : this.parents) { if( this.fieldExclusions.containsKey(obj)) { boolean exclude = isFieldExcludedByObjectInParentsChain(obj, field); if( exclude ) { return true; } } } return false; } private boolean isRootManyValuedFieldExcluded(Object value, String field) { assert value != null; if( !this.excludeManyValuedFields || value != this.root ) { return false; } // Need to check all public and private fields // -maybe in parent classes too Class<?> fieldType = ClassUtils.getFieldType(value.getClass(), field); if( fieldType == null ) return false; return isManyValuedClass(fieldType); } /* package */ boolean isFieldExcludedByObjectInParentsChain(Object obj, String field) { assert obj != null; assert field != null; int positionInChain = this.parents.indexOf(obj); assert positionInChain >= 0; // The last object is a parent or parent of a parent & has fieldExclusions. // We need to check whether the last field is excluded from that object! StringBuilder path = new StringBuilder(); for( int i = positionInChain; i < this.fields.size(); i++) { path.append( this.fields.get(i) ); path.append( '.' ); } path.append( field ); Set<String> objectExclusions = this.fieldExclusions.get(obj); assert objectExclusions != null; boolean excluded = objectExclusions.contains(path.toString()); return excluded; } public void friendOnlyAccess_setRoot(Object root) { if( root != null ) { this.root = root; this.fieldExclusions.put(root, this.rootExclusions); } } public void friendOnlyAccess_dispose() { manager.remove(); } public static boolean isManyValuedClass(Class<?> clazz) { assert clazz != null; if( clazz.isArray() ) { return true; } for( Class<?> manyValuedClass : manyValuedClasses ) { if( manyValuedClass.isAssignableFrom(clazz) ) return true; } return false; } /* Why are we not supporting excludeObjects? * * Because excluded object MUST e to null: due to theway json is parsed by gson, * if you have arrived to the object, you already processed and stored its name, * and then we MUST do someting with it, namely set it to null (or else, * there will be a parse error. * Now, Gson might remove this object or not depending on its * 'serializeNull' settings: but, if it does not, we will end up finding that * whereas excluded objects are set to null, excluede properties are simply * missing. This is a hole in semantics I dislike very much. */ /* public void excludeObjects( Object firstObject, Object... objects ) { assert objects != null; this.exclusions.put( firstObject, new HashSet<String>() ); for( Object obj : objects ) { this.exclusions.put(obj, new HashSet<String>()); } } package-visible boolean isObjectExcluded(Object value) { Set<String> objectExclusions = this.exclusions.get(value); if( objectExclusions == null ) { return false; } return objectExclusions.isEmpty(); } */ }