package org.json.simple.serialization; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ import org.json.simple.parser.BufferedJSONStreamReader; import org.json.simple.parser.JSONStreamReader; import org.json.simple.parser.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.StringReader; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.*; /** * @author karl.wettin@kodapan.se * @since 2009-jul-03 08:20:47 */ public class BeanCodec<T> extends Codec<T> { private static final Logger log = LoggerFactory.getLogger(BeanCodec.class); private void findSuperClasses(Set<Class> classes, Class current) { classes.add(current); if (current.getSuperclass() != null && classes.add(current.getSuperclass())) { findSuperClasses(classes, current.getSuperclass()); } for (Class _class : current.getInterfaces()) { if (classes.add(_class)) { findSuperClasses(classes, _class); } } } private Class<T> beanClass; private CodecRegistry codecRegistry; private Map<String, Field> fieldsByName = new LinkedHashMap<String, Field>(); private T defaultInstance; public Object getDefaultValue(Field field) { return get(defaultInstance, field); } public T getDefaultInstance() { return defaultInstance; } public void resolve(CodecRegistry codecRegistry, Class<T> beanClass) throws IllegalAccessException, InstantiationException { log.info("Reflecting " + beanClass.getName()); if (!Modifier.isAbstract(beanClass.getModifiers()) && !Modifier.isInterface(beanClass.getModifiers()) && !beanClass.isEnum()) { defaultInstance = beanClass.newInstance(); } this.beanClass = beanClass; this.codecRegistry = codecRegistry; Set<Class> allClasses = new HashSet<Class>(); findSuperClasses(allClasses, beanClass); List<Field> allFields = new ArrayList<Field>(); for (Class _class : allClasses) { if (!codecRegistry.getPrimitiveCodecs().containsKey(_class)) { allFields.addAll(Arrays.asList(_class.getDeclaredFields())); } } for (Field field : allFields) { log.info("Reflecting " + beanClass.getName() + "#" + field.getName()); // all static fields are considered transient if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) { continue; } String attribute = field.getName(); JSON annotation = field.getAnnotation(JSON.class); if (annotation != null) { if (annotation.attribute() != null) { attribute = annotation.attribute(); } } fieldsByName.put(attribute, field); } Set<Class> resolved = new HashSet<Class>(); if (resolved.add(getBeanClass())) { for (Field f : getFieldsByName().values()) { if (resolved.add(f.getType())) { codecRegistry.getCodec(f.getType()); } } } } /** * Returns the "primitive" string representation for a given value. * Output often same as String.valueOf(object); * This does not include JSON value syntax such as "", ["",""] and { ... }. * <p/> * Used mostly to marshal primary key values, but all primitives also implement this method. * * @param object instance to be marshaled * @return marshaled valued of parameter object * @throws UnsupportedOperationException if the generic type for this codec can not be serialized to a single primitive value. */ @Override public String marshal(T object) throws IllegalAccessException, InstantiationException { StringWriter sw = new StringWriter(49152); PrintWriter pw = new PrintWriter(sw); marshal(object, object.getClass(), pw, "", 0); pw.flush(); return sw.toString(); } public void marshal(T object, PrintWriter pw) throws InstantiationException, IllegalAccessException { marshal(object, object.getClass(), pw, "", 0); } /** * @see #marshal(Object) */ @Override public T unmarshal(String stringValue) throws InstantiationException, IllegalAccessException { BufferedJSONStreamReader jsr = new BufferedJSONStreamReader(new StringReader(stringValue)); try { jsr.next(); // START DOCUMENT return unmarshal(jsr); } catch (ParseException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } /** * @param bean * @param definedType * @param path * @param indentation @return json representation */ public void marshal(T bean, Class definedType, PrintWriter json, String path, int indentation) throws IllegalAccessException, InstantiationException { if (log.isDebugEnabled()) { log.debug("Marshalling " + path + " " + bean.getClass().getName() + " defined as " + definedType.getName()); } json.append("{\n"); boolean needsComma = false; if (!bean.getClass().equals(definedType)) { addIndentation(json, indentation); json.append("\""); json.append(classIdentifierFieldName); json.append("\" : \""); json.append(bean.getClass().getName()); json.append("\","); } for (Map.Entry<String, Field> fieldEntry : fieldsByName.entrySet()) { if (log.isDebugEnabled() && !"".equals(path)) { log.debug("Marshalling " + path + "#" + fieldEntry.getValue().getName()); } Object value = get(bean, fieldEntry.getValue()); Codec codec = codecRegistry.getCodec(fieldEntry.getValue(), value); if (!codec.isNull(value)) { if (needsComma) { json.append(","); } json.append("\n"); addIndentation(json, indentation); json.append("\""); json.append(fieldEntry.getKey()); json.append("\" : "); StringBuilder childPath = new StringBuilder(); if (path != null) { childPath.append(path); childPath.append("."); } childPath.append(fieldEntry.getKey()); codec.marshal(value, fieldEntry.getValue().getType(), json, childPath.toString(), indentation + 1); needsComma = true; } } json.append("}"); } public T unmarshal(BufferedJSONStreamReader jsr) throws ParseException, IOException, InstantiationException, IllegalAccessException { JSONStreamReader.Event next = jsr.next(); if (next == JSONStreamReader.Event.START_ELEMENT_VALUE) { if (jsr.getObjectValue() == null) { return null; } else { throw new IOException("Expected START_OBJECT or START_ELEMENT_VALUE set to null but got START_ELEMENT_VALUE set to " + jsr.getStringValue()); } } else if (next == JSONStreamReader.Event.START_OBJECT) { return unmarshalBean(jsr); } else { throw new IOException("Expected START_OBJECT or START_ELEMENT_VALUE set to null but got event " + next); } } public T unmarshalBean(BufferedJSONStreamReader jsr) throws ParseException, IOException, InstantiationException, IllegalAccessException { if (log.isDebugEnabled()) { log.debug("Unmarshalling " + beanClass.getName()); } T bean; try { bean = beanClass.newInstance(); } catch (InstantiationException e) { throw new RuntimeException(beanClass.getName(), e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } JSONStreamReader.Event event; Codec codec = null; while ((event = jsr.next()) == JSONStreamReader.Event.START_ELEMENT_KEY) { String fieldName = jsr.getStringValue(); Field field = fieldsByName.get(fieldName); if (log.isDebugEnabled()) { log.debug("Unmarshalling " + beanClass.getName() + "#" + fieldName); } if (field == null && classIdentifierFieldName.equals(jsr.getStringValue())) { jsr.next(); try { if (!beanClass.equals(codecRegistry.getClassResolver().resolve(jsr.getStringValue()))) { throw new RuntimeException("beanClass and JSON class does not match! " + beanClass.getName() + " vs. " + jsr.getStringValue()); } } catch (ClassNotFoundException e) { throw new RuntimeException(e); } event = jsr.next(); if (event != JSONStreamReader.Event.NEXT_VALUE) { break; } else { continue; } } if (field == null) { throw new RuntimeException("There is no codec for field '" + fieldName + "' in class " + beanClass.getName()); } event = jsr.next(); if (event == JSONStreamReader.Event.START_OBJECT) { event = jsr.next(); String aggregateBeanField = jsr.getStringValue(); if (classIdentifierFieldName.equals(aggregateBeanField)) { event = jsr.next(); try { codec = codecRegistry.getCodec(codecRegistry.getClassResolver().resolve(jsr.getStringValue())); jsr.back(3); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } else { event = jsr.back(2); codec = codecRegistry.getCodec(field, null); if (log.isDebugEnabled()) { log.debug("Aggregate bean did not contain top field named \"" + classIdentifierFieldName + "\"."); } } } else { event = jsr.back(); codec = codecRegistry.getCodec(field, null); } set(bean, field, codec.unmarshal(jsr)); event = jsr.next(); if (event != JSONStreamReader.Event.NEXT_VALUE) { break; } // todo end copy to tupplur } if (event != JSONStreamReader.Event.END_OBJECT) { String more; if (codec != null) { more = " Could it be that last running codec '" + codec.getClass().getName() + "' that doesn't read data from the stream?"; } else { more = " Notice that there was no codec executed!"; } throw new RuntimeException("Expected " + JSONStreamReader.Event.END_OBJECT.name() + " but was " + event.name() + "." + more); } return bean; } public Class<T> getBeanClass() { return beanClass; } public CodecRegistry getCodecRegistry() { return codecRegistry; } public Map<String, Field> getFieldsByName() { return fieldsByName; } @Override public String toString() { return "BeanCodec{" + "beanClass=" + beanClass + '}'; } }