001 /* 002 * Copyright © 2008, 2012 Pedro Agulló Soliveres. 003 * 004 * This file is part of DirectJNgine. 005 * 006 * DirectJNgine is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU Lesser General Public License as published by 008 * the Free Software Foundation, either version 3 of the License. 009 * 010 * Commercial use is permitted to the extent that the code/component(s) 011 * do NOT become part of another Open Source or Commercially developed 012 * licensed development library or toolkit without explicit permission. 013 * 014 * DirectJNgine is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU Lesser General Public License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public License 020 * along with DirectJNgine. If not, see <http://www.gnu.org/licenses/>. 021 * 022 * This software uses the ExtJs library (http://extjs.com), which is 023 * distributed under the GPL v3 license (see http://extjs.com/license). 024 */ 025 026 package com.softwarementors.extjs.djn.gson; 027 028 import java.util.Collection; 029 import java.util.Collections; 030 import java.util.HashSet; 031 import java.util.IdentityHashMap; 032 import java.util.Map; 033 import java.util.Set; 034 import java.util.Stack; 035 036 import com.softwarementors.extjs.djn.ClassUtils; 037 import com.softwarementors.extjs.djn.CollectionUtils; 038 import com.softwarementors.extjs.djn.StringUtils; 039 040 import edu.umd.cs.findbugs.annotations.NonNull; 041 042 public class JsonDeserializationManager { 043 private static @NonNull ThreadLocal<JsonDeserializationManager> manager = new ThreadLocal<JsonDeserializationManager>(); 044 private static @NonNull Set<Class<?>> manyValuedClasses = new HashSet<Class<?>>(); 045 046 static { 047 manyValuedClasses.add( Collection.class ); 048 } 049 050 public static void registerManyValuedClasses( Class<?> clazz, Class<?>[] otherClasses) { 051 assert clazz != null; 052 assert otherClasses != null; 053 054 manyValuedClasses.add(clazz); 055 Collections.addAll( manyValuedClasses, otherClasses ); 056 } 057 058 JsonDeserializationManager() { 059 // Avoid instantiation 060 } 061 062 private @NonNull Stack<Object> parents = new Stack<Object>(); 063 private @NonNull Stack<String> fields = new Stack<String>(); 064 private @NonNull Map<Object,Set<String>> fieldExclusions = new IdentityHashMap<Object,Set<String>>(); 065 private @NonNull Set<String> rootExclusions = new HashSet<String>(); 066 private @NonNull Set<String> rootInclusions = new HashSet<String>(); 067 private Object root; 068 private boolean excludeManyValuedFields; 069 070 071 public static JsonDeserializationManager getManager() { 072 if( manager.get() == null ) { 073 manager.set(new JsonDeserializationManager() ); 074 } 075 return manager.get(); 076 } 077 078 public void friendOnlyAccess_pushField(String name) { 079 assert !StringUtils.isEmpty(name); 080 081 this.fields.push(name); 082 } 083 084 public void friendOnlyAccess_popField() { 085 this.fields.pop(); 086 } 087 088 public void friendOnlyAccess_pushParent(Object obj) { 089 assert obj != null; 090 091 this.parents.push(obj); 092 } 093 094 public void friendOnlyAccess_popParent() { 095 this.parents.pop(); 096 } 097 098 public void excludeManyValuedFields() { 099 this.excludeManyValuedFields = true; 100 } 101 102 public void excludeFieldPaths(String firstFieldPath, String... fieldPaths) { 103 assert this.rootExclusions != null; 104 assert firstFieldPath != null; 105 assert fieldPaths != null; 106 107 this.rootInclusions.remove(firstFieldPath); 108 CollectionUtils.removeAll( this.rootInclusions, fieldPaths ); 109 this.rootExclusions.add( firstFieldPath ); 110 Collections.addAll(this.rootExclusions, fieldPaths); 111 } 112 113 public void includeFieldPaths(String firstFieldPath, String... fieldPaths) { 114 assert this.rootInclusions != null; 115 assert firstFieldPath != null; 116 assert fieldPaths != null; 117 118 this.rootExclusions.remove(firstFieldPath); 119 CollectionUtils.removeAll( this.rootExclusions, fieldPaths ); 120 this.rootInclusions.add( firstFieldPath ); 121 Collections.addAll(this.rootInclusions, fieldPaths); 122 } 123 124 /* 125 public void excludeObjectFieldPaths(Object t, String firstFieldPath, String... fieldPaths) { 126 if( t == null ) { 127 return; 128 } 129 130 assert firstFieldPath != null; 131 assert fieldPaths != null; 132 133 Set<String> exclusions = this.fieldExclusions.get(t); 134 if( exclusions == null ) { 135 exclusions = new HashSet<String>(); 136 this.fieldExclusions.put( t, exclusions ); 137 } 138 exclusions.add(firstFieldPath); 139 Collections.addAll( exclusions, fieldPaths); 140 } 141 */ 142 143 public boolean friendOnlyAccess_isFieldExcluded(Object value, String field) { 144 assert value != null; 145 assert field != null; 146 147 if( isRootManyValuedFieldExcluded(value, field)) { 148 return true; 149 } 150 151 if( this.fieldExclusions.isEmpty()) { 152 return false; 153 } 154 155 // value is an special case: can't call hasExclusionForField 156 // because it checks the parents list. 157 // However, check is trivial, because if it has field, it 158 // must be as is, not as a dotted path! 159 if( this.fieldExclusions.containsKey(value)) { 160 Set<String> ex = this.fieldExclusions.get(value); 161 if( ex.contains(field)) { 162 return true; 163 } 164 } 165 166 for( Object obj : this.parents) { 167 if( this.fieldExclusions.containsKey(obj)) { 168 boolean exclude = isFieldExcludedByObjectInParentsChain(obj, field); 169 if( exclude ) { 170 return true; 171 } 172 } 173 } 174 return false; 175 } 176 177 private boolean isRootManyValuedFieldExcluded(Object value, String field) { 178 assert value != null; 179 180 if( !this.excludeManyValuedFields || value != this.root ) { 181 return false; 182 } 183 184 // Need to check all public and private fields 185 // -maybe in parent classes too 186 Class<?> fieldType = ClassUtils.getFieldType(value.getClass(), field); 187 if( fieldType == null ) 188 return false; 189 return isManyValuedClass(fieldType); 190 } 191 192 /* package */ boolean isFieldExcludedByObjectInParentsChain(Object obj, String field) { 193 assert obj != null; 194 assert field != null; 195 int positionInChain = this.parents.indexOf(obj); 196 assert positionInChain >= 0; 197 198 // The last object is a parent or parent of a parent & has fieldExclusions. 199 // We need to check whether the last field is excluded from that object! 200 StringBuilder path = new StringBuilder(); 201 for( int i = positionInChain; i < this.fields.size(); i++) { 202 path.append( this.fields.get(i) ); 203 path.append( '.' ); 204 } 205 path.append( field ); 206 Set<String> objectExclusions = this.fieldExclusions.get(obj); 207 assert objectExclusions != null; 208 209 boolean excluded = objectExclusions.contains(path.toString()); 210 return excluded; 211 } 212 213 public void friendOnlyAccess_setRoot(Object root) { 214 if( root != null ) { 215 this.root = root; 216 this.fieldExclusions.put(root, this.rootExclusions); 217 } 218 } 219 220 public void friendOnlyAccess_dispose() { 221 manager.remove(); 222 } 223 224 public static boolean isManyValuedClass(Class<?> clazz) { 225 assert clazz != null; 226 if( clazz.isArray() ) { 227 return true; 228 } 229 for( Class<?> manyValuedClass : manyValuedClasses ) { 230 if( manyValuedClass.isAssignableFrom(clazz) ) 231 return true; 232 } 233 return false; 234 } 235 236 /* Why are we not supporting excludeObjects? 237 * 238 * Because excluded object MUST e to null: due to theway json is parsed by gson, 239 * if you have arrived to the object, you already processed and stored its name, 240 * and then we MUST do someting with it, namely set it to null (or else, 241 * there will be a parse error. 242 * Now, Gson might remove this object or not depending on its 243 * 'serializeNull' settings: but, if it does not, we will end up finding that 244 * whereas excluded objects are set to null, excluede properties are simply 245 * missing. This is a hole in semantics I dislike very much. 246 */ 247 /* 248 public void excludeObjects( Object firstObject, Object... objects ) { 249 assert objects != null; 250 251 this.exclusions.put( firstObject, new HashSet<String>() ); 252 for( Object obj : objects ) { 253 this.exclusions.put(obj, new HashSet<String>()); 254 } 255 } 256 257 package-visible boolean isObjectExcluded(Object value) { 258 Set<String> objectExclusions = this.exclusions.get(value); 259 if( objectExclusions == null ) { 260 return false; 261 } 262 return objectExclusions.isEmpty(); 263 } 264 */ 265 }