/* * Copyright (c) 2014. by Robusta Code and individual contributors * as indicated by the @authors tag. See the copyright.txt in the * distribution for a full listing of individual contributors. * * 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. */ package io.robusta.rra.representation.implementation; import io.robusta.rra.representation.JsonRepresentation; import io.robusta.rra.representation.Representation; import io.robusta.rra.representation.RepresentationException; import io.robusta.rra.resource.Resource; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.stream.JsonReader; /** * Created by Nicolas Zozol for Robusta Code * <p/> * Licensed 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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. * * @author Nicolas Zozol * */ public class GsonRepresentation implements JsonRepresentation<JsonElement> { Gson gson = new Gson(); JsonElement document; List<String> missingKeys = new ArrayList<String>(5); /** * In that case, is serialization if not null, it's always a JsonObject * * @param serialization */ public GsonRepresentation(HashMap<String, Object> serialization) { this.document = gson.toJsonTree(serialization); } /** * contructor * * @param object */ public GsonRepresentation(Object object) { this.document = gson.toJsonTree(object); } /** * contructor * * @param json */ public GsonRepresentation(String json) { this.document = new JsonParser().parse(json); } /** * contructor * * @param inputStream */ public GsonRepresentation(InputStream inputStream) { this.document = new JsonParser().parse(new JsonReader(new InputStreamReader(inputStream))); } /** * contructor */ public GsonRepresentation() { this.document = null; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#getDocument() */ @Override public JsonElement getDocument() { return this.document; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#get(java.lang.Class) */ @Override public <T> T get(Class<T> type) throws RepresentationException { return get(type, this.document); } // TODO : rename to map() ? /** * convert a jsonElement into Object * * @param type * @param element * @return * @throws RepresentationException */ protected <T> T get(Class<T> type, JsonElement element) throws RepresentationException { if (type == Long.class) { return (T) (Long) element.getAsLong(); } else if (type == Integer.class) { return (T) (Integer) element.getAsInt(); } else if (type == Short.class) { return (T) (Short) element.getAsShort(); } else if (type == Byte.class) { return (T) (Byte) element.getAsByte(); } else if (type == BigInteger.class) { return (T) (BigInteger) element.getAsBigInteger(); } else if (type == Double.class) { return (T) (Double) element.getAsDouble(); } else if (type == Float.class) { return (T) (Float) element.getAsFloat(); } else if (type == BigDecimal.class) { return (T) (BigDecimal) element.getAsBigDecimal(); } else if (type == Boolean.class) { return (T) (Boolean) element.getAsBoolean(); } else if (type == String.class) { return (T) element.getAsString(); } else { return (T) gson.fromJson(element, type); } } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#get(java.lang.String) */ @Override public String get(String key) throws RepresentationException { return this.get(String.class, key); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#get(java.lang.Class, * java.lang.String) */ @Override public <T> T get(Class<T> type, String key) throws RepresentationException { JsonElement element = this.document.getAsJsonObject().get(key); return get(type, element); } /** * check if document has the specified keys * * @param key * @return */ protected boolean has(String key) { throwIfNotObject(this.document); JsonObject object = this.document.getAsJsonObject(); return object.has(key); } /** * check if document is not null * * @param key * @return */ protected boolean hasNotEmpty(String key) { throwIfNotObject(); JsonObject object = this.document.getAsJsonObject(); JsonElement elt = object.get(key); if (elt == null || elt.isJsonNull() || (isString(elt) && elt.getAsString().isEmpty())) { // elt is null or empty string return false; } else { return true; } } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#hasPossiblyEmpty(java.lang * .String[]) */ @Override public boolean hasPossiblyEmpty(String... keys) { boolean result = true; for (String key : keys) { if (!has(key)) { missingKeys.add(key); result = false; } } return result; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#has(java.lang.String[]) */ @Override public boolean has(String... keys) { boolean result = true; for (String key : keys) { if (!hasNotEmpty(key)) { missingKeys.add(key); result = false; } } return result; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#getMissingKeys() */ @Override public List<String> getMissingKeys() { return missingKeys; } /** * if document is null, create a new document */ protected void createObjectIfEmtpy() { if (this.document == null) { this.document = new JsonObject(); } } /** * throw an exception if the jsonElement is null * * @param elt * @throws RepresentationException */ protected void throwIfNull(JsonElement elt) throws RepresentationException { if (elt == null) { throw new RepresentationException("The current element is null"); } } /** * throw an exception if the jsonElement is not an object * * @param elt * @throws RepresentationException */ protected void throwIfNotObject(JsonElement elt) throws RepresentationException { if (!elt.isJsonObject()) { throw new RepresentationException("The current element is not a JSON object but a " + this.getTypeof() + " and thus has no key"); } } /** * throw an exception if the document is not an object * * @throws RepresentationException */ protected void throwIfNotObject() throws RepresentationException { throwIfNull(this.document); throwIfNotObject(this.document); } /** * throw an exception if the jsonElement is not an array * * @param elt * @throws RepresentationException */ protected void throwIfNotArray(JsonElement elt) throws RepresentationException { if (!elt.isJsonArray()) { throw new RepresentationException("The current element is not a JSON array but a " + this.getTypeof() + " and it can't add an object the correct way"); } } /** * throw an exception if the document is not an array * @throws RepresentationException */ protected void throwIfNotArray() throws RepresentationException { throwIfNull(this.document); throwIfNotArray(this.document); } /** * convert the document into a JsonArray and return it. * throw an exception if the document is not an object * @return */ protected JsonObject asObject() { throwIfNotObject(); return this.document.getAsJsonObject(); } /** * convert the document into a JsonArray and return it. * throw an exception if the document is not an array * @return */ protected JsonArray asArray() { throwIfNotArray(); return this.document.getAsJsonArray(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#set(java.lang.String, * java.lang.String) */ @Override public Representation set(String key, String value) { createObjectIfEmtpy(); asObject().add(key, new JsonPrimitive(value)); return this; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#set(java.lang.String, * java.lang.Object) */ @Override public Representation set(String key, Object value) { createObjectIfEmtpy(); asObject().add(key, gson.toJsonTree(value)); return this; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#getValues(java.lang.String) */ @Override public List<String> getValues(String key) throws RepresentationException { List<String> list = new ArrayList<String>(); for (JsonElement elt : asObject().get(key).getAsJsonArray()) { list.add(elt.toString()); } return list; } // TODO : Not tested. If it works here, that's great !! /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#getValues(java.lang.Class, * java.lang.String) */ @Override public <T> List<T> getValues(Class<T> type, String key) throws RepresentationException { List<T> list = new ArrayList<T>(); for (JsonElement elt : asObject().get(key).getAsJsonArray()) { list.add(get(type, elt)); } return list; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#add(java.lang.String, * java.lang.Object) */ @Override public Representation add(String key, Object value) { throwIfNotObject(); throwIfNotArray(this.asObject().get(key)); asObject().get(key).getAsJsonArray().add(gson.toJsonTree(value)); return this; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#add(java.lang.String, * io.robusta.rra.resource.Resource, boolean) */ @Override public Representation add(String key, Resource resource, boolean eager) { throwIfNotObject(); throwIfNotArray(this.asObject().get(key)); // TODO : to use eager, we must look at ReflectiveTypeAdapterFactory // around line 82 JsonElement element = gson.toJsonTree(resource); asObject().get(key).getAsJsonArray().add(element); return this; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#addAll(java.lang.String, * java.util.List) */ @Override public Representation addAll(String key, List values) { throwIfNotObject(); throwIfNotArray(this.asObject().get(key)); JsonArray array = this.asObject().get(key).getAsJsonArray(); for (Object value : values) { array.add(gson.toJsonTree(value)); } return this; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#merge(java.lang.String, * java.lang.String, io.robusta.rra.representation.Representation) */ @Override public Representation merge(String keyForCurrent, String keyForNew, Representation representation) { if (!(representation instanceof GsonRepresentation)) { throw new IllegalArgumentException("Can't merge a GsonRepresentation with a " + representation.getClass().getSimpleName()); } GsonRepresentation mergedRepresentation = new GsonRepresentation(); mergedRepresentation.createObject(); JsonObject object = mergedRepresentation.document.getAsJsonObject(); object.add(keyForCurrent, this.document); object.add(keyForNew, ((GsonRepresentation) representation).document); return mergedRepresentation; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#remove(java.lang.String) */ @Override public Representation remove(String key) throws RepresentationException { int i = 0; if (key.contains(".")) { String[] keys = key.split("\\."); if (keys.length == 0) { throw new IllegalArgumentException("Malformed key " + keys + " ; use something like user.school.id"); } JsonElement current = asObject(); for (i = 0; i < keys.length - 1; i++) { current = current.getAsJsonObject().get(keys[i]); if (current == null) { throw new IllegalArgumentException("There is no valid object for key " + keys[i] + " in '" + key + "'"); } } if (current.getAsJsonObject().get(keys[keys.length - 1]) == null) { throw new IllegalArgumentException("There is no valid object for key " + keys[keys.length - 1] + " in '" + key + "'"); } current.getAsJsonObject().remove(keys[keys.length - 1]); } else { if (asObject().get(key) == null) { throw new IllegalArgumentException("There is no valid object for key " + key); } asObject().remove(key); } return this; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#fetch(java.lang.String) */ @Override public Representation fetch(String key) { throwIfNotObject(); JsonElement root; if (key.contains(".")) { String[] keys = key.split("\\."); if (keys.length == 0) { throw new IllegalArgumentException("Malformed key " + keys + " ; use something like user.school.id"); } String lastKey = keys[0]; JsonElement current = asObject(); for (String newKey : keys) { if (!current.isJsonObject()) { throw new IllegalArgumentException("The key " + lastKey + " in '" + key + "' doesn't point to a Json Object"); } ; current = current.getAsJsonObject().get(newKey); if (current == null) { throw new IllegalArgumentException("There is no valid object for key " + newKey + "in '" + key + "'"); } } root = current; } else { root = asObject().get(key); } if (root == null) { throw new IllegalArgumentException("There is no valid object for key " + key); } else { GsonRepresentation fetchedRepresentation = new GsonRepresentation(); fetchedRepresentation.document = root; return fetchedRepresentation; } } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#copy() */ @Override public Representation copy() { String serialization = gson.toJson(this.document); JsonElement clone = new JsonParser().parse(serialization); GsonRepresentation representation = new GsonRepresentation(); representation.document = clone; return representation; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#createNewRepresentation( * java.lang.Object) */ @Override public Representation createNewRepresentation(Object newObject) { return new GsonRepresentation(newObject); } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#createNewRepresentation( * java.lang.String) */ @Override public Representation createNewRepresentation(String json) { return new GsonRepresentation(json); } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#createNewRepresentation( * java.io.InputStream) */ @Override public Representation createNewRepresentation(InputStream inputStream) { return new GsonRepresentation(inputStream); } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#createNewRepresentation() */ @Override public Representation createNewRepresentation() { return new GsonRepresentation(); } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.JsonRepresentation#addToArray(io.robusta * .rra.resource.Resource, boolean) */ @Override public Representation addToArray(Resource resource, boolean eager) { throwIfNotArray(); // TODO : to use eager, we must look at ReflectiveTypeAdapterFactory // around line 82 JsonElement element = gson.toJsonTree(resource); asArray().add(element); return this; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.JsonRepresentation#addToArray(java.lang * .Object) */ @Override public Representation addToArray(Object value) { throwIfNotArray(); JsonElement element = gson.toJsonTree(value); asArray().add(element); return this; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.JsonRepresentation#pluck(java.lang.Class, * java.lang.String) */ @Override public <T> List<T> pluck(Class<T> type, String key) throws RepresentationException { throwIfNotArray(); List<T> result = new ArrayList<T>(); for (JsonElement elt : asArray()) { result.add(get(type, elt)); } return result; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isPrimitive() */ @Override public boolean isPrimitive() { return this.document.isJsonPrimitive(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isObject() */ @Override public boolean isObject() { return this.document.isJsonObject(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isBoolean() */ @Override public boolean isBoolean() { // checking that it' a primitive if (this.document.isJsonPrimitive()) { String json = this.document.toString(); return json.equalsIgnoreCase("true") || json.equalsIgnoreCase("false"); } else { return false; } } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isString() */ @Override public boolean isString() { return isString(this.document); } /** * @param elt * @return */ private boolean isString(JsonElement elt) { return elt.isJsonPrimitive() && (elt.toString().trim().startsWith("\"") || elt.toString().trim().startsWith("'")); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isNumber() */ @Override public boolean isNumber() { return this.document.isJsonPrimitive() && !this.isBoolean() && !this.isString(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isArray() */ @Override public boolean isArray() { return this.document.isJsonArray(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isNull() */ @Override public boolean isNull() { return this.document.isJsonNull(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#getTypeof() */ @Override public JsonType getTypeof() { if (this.isString()) { return JsonType.STRING; } else if (this.isArray()) { return JsonType.ARRAY; } else if (this.isBoolean()) { return JsonType.BOOLEAN; } else if (this.isNumber()) { return JsonType.NUMBER; } else if (this.isNull()) { return JsonType.NULL; } else if (this.isObject()) { return JsonType.OBJECT; } else throw new IllegalStateException("Can't find the type of this document"); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#createObject() */ @Override public Representation<JsonElement> createObject() { if (this.document != null) { throw new IllegalStateException( "This representation is not Empty. Use createNewRepresentation() to get a new empty representation"); } this.document = new JsonObject(); return this; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#createArray() */ @Override public Representation<JsonElement> createArray() { if (this.document != null) { throw new IllegalStateException( "This representation is not Empty. Use createNewRepresentation() to get a new empty representation"); } this.document = new JsonArray(); return this; } /** * * @param type * @return */ protected JsonType getJsonType(Class type) { if (type == Integer.class || type == Long.class || type == Integer.class || type == Integer.class) { return JsonType.NUMBER; } else if (type == String.class) { } return null; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return this.document.toString(); } }