blob: 8668856a6da6a98c7996e7d8222385ae4f10b05a [file] [log] [blame]
/*
* 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 org.apache.felix.cm.json.impl;
import java.io.FilterReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Map;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonNumber;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;
import javax.json.JsonString;
import javax.json.JsonStructure;
import javax.json.JsonValue;
import javax.json.JsonValue.ValueType;
import org.apache.felix.cm.json.Configurations;
import org.osgi.util.converter.Converters;
public class JsonSupport {
/**
* Convert a json value into an object
*
* @param value The json value
* @return The object or {@code null} if the provided value is {@code null} or
* {@code ValueType#NULL}
*/
public static Object convertToObject(final JsonValue value) {
if (value == null) {
return null;
}
switch (value.getValueType()) {
// type NULL -> return null
case NULL:
return null;
// type TRUE or FALSE -> return boolean
case FALSE:
return false;
case TRUE:
return true;
// type String -> return String
case STRING:
return ((JsonString) value).getString();
// type Number -> return long or double
case NUMBER:
final JsonNumber num = (JsonNumber) value;
if (num.isIntegral()) {
return num.longValue();
}
return num.doubleValue();
// type ARRAY -> return list and call this method for each value
case ARRAY:
final JsonArray array = value.asJsonArray();
final ValueType arrayType = detectArrayType(array);
final Object[] objArray;
if (arrayType == ValueType.FALSE || arrayType == ValueType.TRUE) {
objArray = new Boolean[array.size()];
} else if (arrayType == ValueType.NUMBER) {
objArray = new Object[array.size()];
} else if (arrayType == ValueType.STRING || arrayType == ValueType.OBJECT) {
objArray = new String[array.size()];
} else {
objArray = null;
}
if (objArray != null) {
boolean isLong = true;
for (int i = 0; i < array.size(); i++) {
objArray[i] = convertToObject(array.get(i));
if ( arrayType == ValueType.NUMBER && !(objArray[i] instanceof Long) ) {
isLong = false;
}
}
if ( arrayType == ValueType.NUMBER ) {
if ( isLong ) {
return Converters.standardConverter().convert(objArray).to(Long[].class);
} else {
return Converters.standardConverter().convert(objArray).to(Double[].class);
}
}
return objArray;
}
return array.toString();
// type OBJECT -> return map
default:
return value.asJsonObject().toString();
}
}
/**
* Detect the value type of a json array. If all elements in the array have the
* same type, this type is returned. Otherwise {@code null} is
* returned. For an empty array {@code ValueType#STRING} is returned.
*
* @param array The array
* @return The detected type
*/
private static ValueType detectArrayType(final JsonArray array) {
if (array.size() == 0) {
return ValueType.STRING;
}
final ValueType vt = array.get(0).getValueType();
for (int i = 1; i < array.size(); i++) {
final ValueType ct = array.get(i).getValueType();
boolean isSame = false;
if (ct == vt) {
isSame = true;
} else if (vt == ValueType.TRUE && ct == ValueType.FALSE) {
isSame = true;
} else if (vt == ValueType.FALSE && ct == ValueType.TRUE) {
isSame = true;
}
if (!isSame) {
return null;
}
}
return vt;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static JsonValue convertToJson(final Object object) {
if (object == null) {
return JsonValue.NULL;
} else if (object instanceof String) {
return Json.createValue((String) object);
} else if (object instanceof Long) {
return Json.createValue((Long) object);
} else if (object instanceof Integer) {
return Json.createValue((Integer) object);
} else if (object instanceof Short) {
return Json.createValue((Short) object);
} else if (object instanceof Byte) {
return Json.createValue((Byte) object);
} else if (object instanceof Float) {
return Json.createValue((Float) object);
} else if (object instanceof Double) {
return Json.createValue((Double) object);
} else if (object instanceof Boolean) {
final Boolean value = (Boolean) object;
return value ? JsonValue.TRUE : JsonValue.FALSE;
} else if (object instanceof Map) {
boolean valid = true;
final JsonObjectBuilder builder = Json.createObjectBuilder();
for (final Map.Entry<Object, Object> entry : ((Map<Object, Object>) object).entrySet()) {
final JsonValue value = convertToJson(entry.getValue());
if (entry.getKey() instanceof String) {
builder.add(entry.getKey().toString(), value);
} else {
valid = false;
break;
}
}
if (valid) {
return builder.build();
}
} else if (object instanceof Collection) {
final JsonArrayBuilder builder = Json.createArrayBuilder();
for (final Object obj : (Collection) object) {
builder.add(convertToJson(obj));
}
return builder.build();
} else if (object.getClass().isArray()) {
final JsonArrayBuilder builder = Json.createArrayBuilder();
for (int i = 0; i < Array.getLength(object); i++) {
builder.add(convertToJson(Array.get(object, i)));
}
return builder.build();
}
return Json.createValue(object.toString());
}
/**
* Parse a JSON content
*
* @param identifier The identifier of the JSON contents (optional)
*
* @param contentsReader The reader for the contents
*
* @return The parsed JSON object. throws IOException on failure parsing
*/
public static JsonObject parseJson(final String identifier, final Reader contentsReader) throws IOException {
try (final JsonReader reader = Json.createReader(Configurations.jsonCommentAwareReader(new FilterReader(contentsReader) {
@Override
public void close() throws IOException {
// do not close reader
}
}))) {
final JsonStructure obj = reader.read();
if (obj != null && obj.getValueType() == ValueType.OBJECT) {
return (JsonObject) obj;
}
final String msg = "Invalid JSON, no root object";
if (identifier != null) {
throw new IOException(identifier.concat(" : ").concat(msg));
}
throw new IOException(msg);
}
}
/**
* Create a reader which removes comments from the input
* @param reader The input reader
* @return The output reader
* @throws IOException If something fails
*/
public static Reader createCommentRemovingReader(final Reader reader) throws IOException {
final String contents;
try (final StringWriter writer = new StringWriter() ){
final char[] buf = new char[2048];
int l;
while ( (l = reader.read(buf)) > 0 ) {
writer.write(buf, 0, l);
}
writer.flush();
contents = writer.toString();
}
final StringReader stringReader = new StringReader(removeComments(contents));
return new FilterReader(stringReader) {
boolean closed = false;
@Override
public void close() throws IOException {
if (!closed) {
closed = true;
reader.close();
super.close();
}
}
};
}
/**
* Helper method to remove comments from JSON
* @param comments The JSON with comments
* @return The JSON without comments
*/
private static String removeComments(final String comments) {
final StringBuilder sb = new StringBuilder(comments);
int index = 0;
boolean insideQuote = false;
while ( index < sb.length()) {
switch ( sb.charAt(index) ) {
case '"' : if ( index == 0 || sb.charAt(index - 1) != '\\') {
insideQuote = !insideQuote;
}
index++;
break;
case '/' : if ( !insideQuote && index + 1 < sb.length()) {
if ( sb.charAt(index + 1) == '/') {
// line comment
int m = index + 2;
while ( m < sb.length() && sb.charAt(m) != '\n' ) {
m++;
}
sb.delete(index, m);
} else if ( sb.charAt(index + 1 ) == '*') {
// block comment
int m = index + 2;
int newlines = 0;
while ( m < sb.length() && (sb.charAt(m) != '/' || sb.charAt(m - 1) != '*')) {
if ( sb.charAt(m) == '\n') {
newlines++;
}
m++;
}
if ( m == sb.length() ) {
index = m; // invalid - just go to the end
} else {
sb.delete(index, m+1);
for(int x = 0; x<newlines; x++) {
sb.insert(index, '\n');
}
}
}
}
index++;
break;
default: index++;
}
}
return sb.toString();
}
}