001package ball.databind; 002/*- 003 * ########################################################################## 004 * Data Binding Utilities 005 * $Id: PolymorphicTypeMap.java 6118 2020-06-04 19:31:45Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-databind/trunk/src/main/java/ball/databind/PolymorphicTypeMap.java $ 007 * %% 008 * Copyright (C) 2016 - 2020 Allen D. Ball 009 * %% 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * ########################################################################## 022 */ 023import ball.util.PropertiesImpl; 024import com.fasterxml.jackson.core.JsonParser; 025import com.fasterxml.jackson.core.ObjectCodec; 026import com.fasterxml.jackson.databind.BeanDescription; 027import com.fasterxml.jackson.databind.DeserializationConfig; 028import com.fasterxml.jackson.databind.DeserializationContext; 029import com.fasterxml.jackson.databind.JsonDeserializer; 030import com.fasterxml.jackson.databind.JsonNode; 031import com.fasterxml.jackson.databind.deser.AbstractDeserializer; 032import com.fasterxml.jackson.databind.deser.BeanDeserializer; 033import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; 034import com.fasterxml.jackson.databind.node.TreeTraversingParser; 035import java.beans.BeanInfo; 036import java.beans.PropertyDescriptor; 037import java.io.IOException; 038import java.io.InputStream; 039import java.util.ArrayList; 040import java.util.Collection; 041import java.util.TreeMap; 042import java.util.TreeSet; 043import lombok.ToString; 044import org.apache.commons.lang3.StringUtils; 045 046import static java.beans.Introspector.getBeanInfo; 047import static java.util.Comparator.comparing; 048import static java.util.Objects.requireNonNull; 049 050/** 051 * Class suitable for mapping polymorphic subtypes. Subclasses can specify 052 * the mapping in a {@link java.util.Properties} resource which will be 053 * automatically loaded on instantiation. 054 * 055 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 056 * @version $Revision: 6118 $ 057 */ 058public abstract class PolymorphicTypeMap extends TreeMap<Class<?>,Class<?>[]> { 059 private static final long serialVersionUID = -4465979259676184876L; 060 061 /** 062 * Sole constructor. 063 */ 064 protected PolymorphicTypeMap() { 065 super(comparing(Class::getName)); 066 067 try { 068 PropertiesImpl properties = new PropertiesImpl(); 069 String name = getClass().getSimpleName() + ".properties"; 070 InputStream in = getClass().getResourceAsStream(name); 071 072 if (in != null) { 073 try { 074 properties.load(in); 075 } finally { 076 in.close(); 077 } 078 } 079 080 ClassLoader loader = getClass().getClassLoader(); 081 Package pkg = getClass().getPackage(); 082 083 for (String key : properties.stringPropertyNames()) { 084 TreeSet<Class<?>> value = 085 new TreeSet<>(comparing(Class::getName)); 086 087 for (String substring : 088 properties.getProperty(key) 089 .split("[,\\p{Space}]+")) { 090 substring = substring.trim(); 091 092 if (! StringUtils.isEmpty(substring)) { 093 value.add(getClassFor(loader, pkg, substring)); 094 } 095 } 096 097 put(getClassFor(loader, pkg, key), 098 value.toArray(new Class<?>[] { })); 099 } 100 } catch (Exception exception) { 101 throw new ExceptionInInitializerError(exception); 102 } 103 } 104 105 private Class<?> getClassFor(ClassLoader loader, 106 Package pkg, String name) throws Exception { 107 Class<?> cls = null; 108 109 try { 110 cls = Class.forName(pkg.getName() + "." + name, true, loader); 111 } catch (Exception exception) { 112 cls = Class.forName(name, true, loader); 113 } 114 115 return cls; 116 } 117 118 /** 119 * Method to get a {@link BeanDeserializerModifier} for this 120 * {@link PolymorphicTypeMap}. 121 * 122 * @return The {@link BeanDeserializerModifier}. 123 */ 124 public BeanDeserializerModifier getBeanDeserializerModifier() { 125 return new DeserializerModifier(); 126 } 127 128 /** 129 * Callback from {@link JsonDeserializer} implementation to allow 130 * subclass implementations to initialize the resulting bean with the 131 * parse-tree ({@link JsonNode}). 132 * 133 * @param object The {@link Object} to initialize. 134 * @param codec The {@link ObjectCodec}. 135 * @param node The {@link JsonNode}. 136 * 137 * @throws IOException If there is a problem initializing the 138 * {@link JSONBean}. 139 */ 140 protected void initialize(Object object, 141 ObjectCodec codec, 142 JsonNode node) throws IOException { 143 } 144 145 @Override 146 public Class<?>[] put(Class<?> key, Class<?>[] value) { 147 for (Class<?> subtype : value) { 148 if (! key.isAssignableFrom(subtype)) { 149 throw new IllegalArgumentException(subtype.getName() 150 + " is not a subclass of " 151 + key.getName()); 152 } 153 } 154 155 return super.put(key, value); 156 } 157 158 @ToString 159 private class DeserializerModifier extends BeanDeserializerModifier { 160 public DeserializerModifier() { super(); } 161 162 @Override 163 public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, 164 BeanDescription description, 165 JsonDeserializer<?> deserializer) { 166 Class<?> key = description.getBeanClass(); 167 168 if (containsKey(key)) { 169 deserializer = 170 (deserializer instanceof BeanDeserializer) 171 ? new BeanDeserializerImpl((BeanDeserializer) deserializer, 172 key, get(key)) 173 : new AbstractDeserializerImpl(description, 174 key, get(key)); 175 } 176 177 return deserializer; 178 } 179 180 private class BeanInfoList extends ArrayList<BeanInfo> { 181 private static final long serialVersionUID = 1008165295877024242L; 182 183 private final Class<?> supertype; 184 185 public BeanInfoList(Class<?> supertype, Class<?>... subtypes) { 186 super(subtypes.length); 187 188 this.supertype = requireNonNull(supertype, "supertype"); 189 190 try { 191 for (Class<?> subtype : subtypes) { 192 if (supertype.isAssignableFrom(subtype)) { 193 add(getBeanInfo(subtype, supertype)); 194 } else { 195 throw new IllegalArgumentException(subtype.getName() 196 + " is not a subclass of " 197 + supertype.getName()); 198 } 199 } 200 } catch (Exception exception) { 201 throw new ExceptionInInitializerError(exception); 202 } 203 } 204 205 public Class<?> supertype() { return supertype; } 206 207 public Class<?> subtypeFor(JsonNode node) { 208 Class<?> subtype = null; 209 210 for (BeanInfo info : this) { 211 if (hasAll(node, info.getPropertyDescriptors())) { 212 subtype = 213 info.getBeanDescriptor() 214 .getBeanClass() 215 .asSubclass(supertype); 216 break; 217 } 218 } 219 220 return subtype; 221 } 222 223 private boolean hasAll(JsonNode node, 224 PropertyDescriptor... properties) { 225 boolean hasAll = true; 226 227 for (PropertyDescriptor property : properties) { 228 hasAll &= node.has(property.getName()); 229 230 if (! hasAll) { 231 break; 232 } 233 } 234 235 return hasAll; 236 } 237 } 238 239 @ToString 240 private class JsonParserImpl extends TreeTraversingParser { 241 protected JsonNode node = null; 242 243 public JsonParserImpl(JsonParser parser) throws IOException { 244 this((JsonNode) parser.readValueAsTree(), parser.getCodec()); 245 } 246 247 private JsonParserImpl(JsonNode node, ObjectCodec codec) { 248 super(node, codec); 249 250 this.node = requireNonNull(node, "node"); 251 } 252 } 253 254 @ToString 255 private class BeanDeserializerImpl extends BeanDeserializer { 256 private static final long serialVersionUID = 7600217898656123257L; 257 258 private final BeanInfoList list; 259 260 public BeanDeserializerImpl(BeanDeserializer deserializer, 261 Class<?> supertype, 262 Class<?>... subtypes) { 263 super(deserializer); 264 265 list = new BeanInfoList(supertype, subtypes); 266 } 267 268 @Override 269 public Class<?> handledType() { return list.supertype(); } 270 271 @Override 272 public Object deserialize(JsonParser parser, 273 DeserializationContext context) throws IOException { 274 Object object = null; 275 ObjectCodec codec = parser.getCodec(); 276 JsonNode node = null; 277 278 if (parser instanceof JsonParserImpl) { 279 node = ((JsonParserImpl) parser).node; 280 281 if (node != null) { 282 ((JsonParserImpl) parser).node = null; 283 } 284 } 285 286 if (node != null) { 287 Class<?> subtype = list.subtypeFor(node); 288 289 if (subtype != null) { 290 object = codec.readValue(parser, subtype); 291 } else { 292 object = super.deserialize(parser, context); 293 } 294 295 initialize(list.supertype().cast(object), codec, node); 296 } else { 297 object = 298 codec.readValue(new JsonParserImpl(parser), 299 list.supertype()); 300 } 301 302 return object; 303 } 304 } 305 306 @ToString 307 private class AbstractDeserializerImpl extends AbstractDeserializer { 308 private static final long serialVersionUID = -7887072343275693448L; 309 310 private final BeanInfoList list; 311 312 public AbstractDeserializerImpl(BeanDescription description, 313 Class<?> supertype, 314 Class<?>... subtypes) { 315 super(description); 316 317 list = new BeanInfoList(supertype, subtypes); 318 } 319 320 @Override 321 public Class<?> handledType() { return list.supertype(); } 322 323 @Override 324 public Object deserialize(JsonParser parser, 325 DeserializationContext context) throws IOException { 326 Object object = null; 327 ObjectCodec codec = parser.getCodec(); 328 JsonNode node = null; 329 330 if (parser instanceof JsonParserImpl) { 331 node = ((JsonParserImpl) parser).node; 332 333 if (node != null) { 334 ((JsonParserImpl) parser).node = null; 335 } 336 } 337 338 if (node != null) { 339 Class<?> subtype = list.subtypeFor(node); 340 341 if (subtype != null) { 342 object = codec.readValue(parser, subtype); 343 } else { 344 object = super.deserialize(parser, context); 345 } 346 347 initialize(list.supertype().cast(object), codec, node); 348 } else { 349 object = 350 codec.readValue(new JsonParserImpl(parser), 351 list.supertype()); 352 } 353 354 return object; 355 } 356 } 357 } 358}