/******************************************************************************* * This file is part of RedReader. * * RedReader is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RedReader 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RedReader. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package org.quantumbadger.redreader.jsonwrap; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * A JSON object, which may be partially or fully received. */ public final class JsonBufferedObject extends JsonBuffered implements Iterable<Map.Entry<String, JsonValue>> { private final HashMap<String, JsonValue> properties = new HashMap<>(); @Override protected void buildBuffered(final JsonParser jp) throws IOException { JsonToken jt; while((jt = jp.nextToken()) != JsonToken.END_OBJECT) { if(jt != JsonToken.FIELD_NAME) throw new JsonParseException("Expecting field name, got " + jt.name(), jp.getCurrentLocation()); final String fieldName = jp.getCurrentName(); final JsonValue value = new JsonValue(jp); synchronized(this) { properties.put(fieldName, value); notifyAll(); } value.buildInThisThread(); } } /** * This method will block until either: the specified field is received, the * object fails to parse, or the object is fully received and there is no * such field. * * @param name * The name of the field * @return The value contained in the specified field * @throws InterruptedException * @throws java.io.IOException */ public JsonValue get(final String name) throws InterruptedException, IOException { synchronized(this) { while(getStatus() == STATUS_LOADING && !properties.containsKey(name)) { wait(); } if(getStatus() != STATUS_FAILED || properties.containsKey(name)) { return properties.get(name); } if(getStatus() == STATUS_FAILED) { throwFailReasonException(); } return null; } } /** * This method will block until either: the specified field is received, the * object fails to parse, or the object is fully received and there is no * such field. * * @param name * The name of the field * @return The value contained in the specified field * @throws InterruptedException * @throws java.io.IOException */ public String getString(final String name) throws InterruptedException, IOException { final JsonValue jsonValue = get(name); return jsonValue == null ? null : jsonValue.asString(); } /** * This method will block until either: the specified field is received, the * object fails to parse, or the object is fully received and there is no * such field. * * @param name * The name of the field * @return The value contained in the specified field * @throws InterruptedException * @throws java.io.IOException */ public Long getLong(final String name) throws InterruptedException, IOException { return get(name).asLong(); } /** * This method will block until either: the specified field is received, the * object fails to parse, or the object is fully received and there is no * such field. * * @param name * The name of the field * @return The value contained in the specified field * @throws InterruptedException * @throws java.io.IOException */ public Double getDouble(final String name) throws InterruptedException, IOException { return get(name).asDouble(); } /** * This method will block until either: the specified field is received, the * object fails to parse, or the object is fully received and there is no * such field. * * @param name * The name of the field * @return The value contained in the specified field * @throws InterruptedException * @throws java.io.IOException */ public Boolean getBoolean(final String name) throws InterruptedException, IOException { return get(name).asBoolean(); } /** * This method will block until either: the specified field is received, the * object fails to parse, or the object is fully received and there is no * such field. * * @param name * The name of the field * @return The value contained in the specified field * @throws InterruptedException * @throws java.io.IOException */ public JsonBufferedObject getObject(final String name) throws InterruptedException, IOException { return get(name).asObject(); } public <E> E getObject(final String name, final Class<E> clazz) throws InterruptedException, IOException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { return get(name).asObject(clazz); } /** * This method will block until either: the specified field is received, the * object fails to parse, or the object is fully received and there is no * such field. * * @param name * The name of the field * @return The value contained in the specified field * @throws InterruptedException * @throws java.io.IOException */ public JsonBufferedArray getArray(final String name) throws InterruptedException, IOException { return get(name).asArray(); } @Override protected void prettyPrint(final int indent, final StringBuilder sb) throws InterruptedException, IOException { if(join() != STATUS_LOADED) { throwFailReasonException(); } sb.append('{'); final Set<String> propertyKeySet = properties.keySet(); final String[] fieldNames = propertyKeySet.toArray(new String[propertyKeySet.size()]); for(int prop = 0; prop < fieldNames.length; prop++) { if(prop != 0) sb.append(','); sb.append('\n'); for(int i = 0; i < indent + 1; i++) sb.append(" "); sb.append("\"").append(fieldNames[prop].replace("\\", "\\\\").replace("\"", "\\\"")).append("\": "); properties.get(fieldNames[prop]).prettyPrint(indent + 1, sb); } sb.append('\n'); for(int i = 0; i < indent; i++) sb.append(" "); sb.append('}'); } public <E> E asObject(final Class<E> clazz) throws InstantiationException, IllegalAccessException, InterruptedException, IOException, NoSuchMethodException, InvocationTargetException { final E obj = clazz.getConstructor().newInstance(); populateObject(obj); return obj; } public void populateObject(final Object o) throws InterruptedException, IOException, IllegalArgumentException, InstantiationException, NoSuchMethodException, InvocationTargetException { if(join() != STATUS_LOADED) { throwFailReasonException(); } final Field[] objectFields = o.getClass().getFields(); try { for(final Field objectField : objectFields) { if((objectField.getModifiers() & Modifier.TRANSIENT) != 0) { continue; } final JsonValue val; if(properties.containsKey(objectField.getName())) { val = properties.get(objectField.getName()); } else if(objectField.getName().startsWith("_json_")) { val = properties.get(objectField.getName().substring("_json_".length())); } else { val = null; } if(val == null) { continue; } objectField.setAccessible(true); final Class<?> fieldType = objectField.getType(); if(fieldType == Long.class || fieldType == Long.TYPE) { objectField.set(o, val.asLong()); } else if(fieldType == Double.class || fieldType == Double.TYPE) { objectField.set(o, val.asDouble()); } else if(fieldType == Integer.class || fieldType == Integer.TYPE) { objectField.set(o, val.isNull() ? null : val.asLong().intValue()); } else if(fieldType == Float.class || fieldType == Float.TYPE) { objectField.set(o, val.isNull() ? null : val.asDouble().floatValue()); } else if(fieldType == Boolean.class || fieldType == Boolean.TYPE) { objectField.set(o, val.asBoolean()); } else if(fieldType == String.class) { objectField.set(o, val.asString()); } else if(fieldType == JsonBufferedArray.class) { objectField.set(o, val.asArray()); } else if(fieldType == JsonBufferedObject.class) { objectField.set(o, val.asObject()); } else if(fieldType == JsonValue.class) { objectField.set(o, val); } else if(fieldType == Object.class) { final Object result; switch(val.getType()) { case JsonValue.TYPE_BOOLEAN: result = val.asBoolean(); break; case JsonValue.TYPE_INTEGER: result = val.asLong(); break; case JsonValue.TYPE_STRING: result = val.asString(); break; case JsonValue.TYPE_FLOAT: result = val.asDouble(); break; default: result = val; } objectField.set(o, result); } else { objectField.set(o, val.asObject(fieldType)); } } } catch(IllegalAccessException e) { throw new RuntimeException(e); } } public Iterator<Map.Entry<String, JsonValue>> iterator() { try { join(); } catch (InterruptedException e) { throw new RuntimeException(e); } return properties.entrySet().iterator(); } }