blob: 7d09e4ac595539e79cd59671af2554c7996b03b8 [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.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonValue;
import org.osgi.util.converter.Converters;
import org.osgi.util.converter.TypeReference;
public class TypeConverter {
public static final String TYPE_BINARY = "binary";
public static final String TYPE_BINARIES = "binary[]";
private static final String TYPE_COLLECTION = "Collection";
public static final String NO_TYPE_INFO = "";
public static final Object CONVERSION_FAILED = new Object();
private static final Map<String, Class<?>> TYPE_MAP = new LinkedHashMap<>();
private static final Map<String, TypeReference<?>> TYPE_COLLECTION_MAP = new LinkedHashMap<>();
static {
// scalar types and primitive types
TYPE_MAP.put("String", String.class);
TYPE_MAP.put("Integer", Integer.class);
TYPE_MAP.put("int", Integer.class);
TYPE_MAP.put("Long", Long.class);
TYPE_MAP.put("long", Long.class);
TYPE_MAP.put("Float", Float.class);
TYPE_MAP.put("float", Float.class);
TYPE_MAP.put("Double", Double.class);
TYPE_MAP.put("double", Double.class);
TYPE_MAP.put("Byte", Byte.class);
TYPE_MAP.put("byte", Byte.class);
TYPE_MAP.put("Short", Short.class);
TYPE_MAP.put("short", Short.class);
TYPE_MAP.put("Character", Character.class);
TYPE_MAP.put("char", Character.class);
TYPE_MAP.put("Boolean", Boolean.class);
TYPE_MAP.put("boolean", Boolean.class);
// array of scalar types and primitive types
TYPE_MAP.put("String[]", String[].class);
TYPE_MAP.put("int[]", int[].class);
TYPE_MAP.put("Integer[]", Integer[].class);
TYPE_MAP.put("long[]", long[].class);
TYPE_MAP.put("Long[]", Long[].class);
TYPE_MAP.put("float[]", float[].class);
TYPE_MAP.put("Float[]", Float[].class);
TYPE_MAP.put("double[]", double[].class);
TYPE_MAP.put("Double[]", Double[].class);
TYPE_MAP.put("byte[]", byte[].class);
TYPE_MAP.put("Byte[]", Byte[].class);
TYPE_MAP.put("short[]", short[].class);
TYPE_MAP.put("Short[]", Short[].class);
TYPE_MAP.put("boolean[]", boolean[].class);
TYPE_MAP.put("Boolean[]", Boolean[].class);
TYPE_MAP.put("char[]", char[].class);
TYPE_MAP.put("Character[]", Character[].class);
// binaries
TYPE_MAP.put(TYPE_BINARY, String.class);
TYPE_MAP.put(TYPE_BINARIES, String[].class);
// Collections of scalar types
TYPE_COLLECTION_MAP.put("Collection<String>", new TypeReference<ArrayList<String>>() {
});
TYPE_COLLECTION_MAP.put("Collection<Integer>", new TypeReference<ArrayList<Integer>>() {
});
TYPE_COLLECTION_MAP.put("Collection<Long>", new TypeReference<ArrayList<Long>>() {
});
TYPE_COLLECTION_MAP.put("Collection<Float>", new TypeReference<ArrayList<Float>>() {
});
TYPE_COLLECTION_MAP.put("Collection<Double>", new TypeReference<ArrayList<Double>>() {
});
TYPE_COLLECTION_MAP.put("Collection<Byte>", new TypeReference<ArrayList<Byte>>() {
});
TYPE_COLLECTION_MAP.put("Collection<Short>", new TypeReference<ArrayList<Short>>() {
});
TYPE_COLLECTION_MAP.put("Collection<Character>", new TypeReference<ArrayList<Character>>() {
});
TYPE_COLLECTION_MAP.put("Collection<Boolean>", new TypeReference<ArrayList<Boolean>>() {
});
}
/**
* Convert a value to the given type
*
* @param value The json value
* @param typeInfo Optional type info
* @return The converted value or {@code CONVERSION_FAILED} if the conversion failed.
* @throws IllegalArgumentException If the type is unknown
*/
public static Object convertObjectToType(
final JsonValue jsonValue,
final String typeInfo) throws IllegalArgumentException {
final Object value = JsonSupport.convertToObject(jsonValue);
if (typeInfo == null) {
return value;
}
final Class<?> typeClass = TYPE_MAP.get(typeInfo);
if ( typeClass != null ) {
Object result = Converters.standardConverter().convert(value).defaultValue(null).to(typeClass);
if (result == null && value != null) {
result = CONVERSION_FAILED;
}
return result;
}
final TypeReference<?> typeReference = TYPE_COLLECTION_MAP.get(typeInfo);
if (typeReference != null) {
if (value == null) {
return Collections.EMPTY_LIST;
}
Object result = Converters.standardConverter().convert(value).defaultValue(null).to(typeReference);
if (result == null) {
result = CONVERSION_FAILED;
}
return result;
}
// raw collection
if (TYPE_COLLECTION.equals(typeInfo)) {
if (value == null) {
return Collections.EMPTY_LIST;
} else if (value instanceof String || value instanceof Boolean || value instanceof Long
|| value instanceof Double) {
return Collections.singletonList(value);
}
final Collection<Object> c = new ArrayList<>();
for (int i = 0; i < Array.getLength(value); i++) {
c.add(Array.get(value, i));
}
return Collections.unmodifiableCollection(c);
}
// unknown type
return CONVERSION_FAILED;
}
private static String findType(final Class<?> clazz) {
for (final Map.Entry<String, Class<?>> entry : TYPE_MAP.entrySet()) {
if (clazz.isAssignableFrom(entry.getValue())) {
return entry.getKey();
}
}
return null;
}
/**
* Convert an object to a {@code JsonValue}.
*
* @param value The object to convert
* @return A map entry where the key contains the type info and the value the
* converted JsonValue.
*/
public static Map.Entry<String, JsonValue> convertObjectToTypedJsonValue(final Object value) {
// native types
if (value == null || value instanceof Long || value instanceof Double || value instanceof String
|| value instanceof Boolean) {
return new Entry(NO_TYPE_INFO, JsonSupport.convertToJson(value));
}
if (value.getClass().isArray()) {
// arrays
String typeInfo = findType(value.getClass());
if (typeInfo != null) {
if ("String[]".equals(typeInfo) || "Boolean[]".equals(typeInfo) || "Long[]".equals(typeInfo)
|| "Double[]".equals(typeInfo)) {
typeInfo = NO_TYPE_INFO;
}
final JsonArrayBuilder jab = Json.createArrayBuilder();
for (int i = 0; i < Array.getLength(value); i++) {
jab.add(convertObjectToTypedJsonValue(Array.get(value, i)).getValue());
}
return new Entry(typeInfo, jab.build());
}
} else if (Collection.class.isAssignableFrom(value.getClass())) {
// collections
final Collection<?> collection = (Collection<?>) value;
// get first object to get the type
String typeInfo = TypeConverter.TYPE_COLLECTION;
final Iterator<?> i = collection.iterator();
if (i.hasNext()) {
final String colType = findType(i.next().getClass());
if (colType != null) {
typeInfo = typeInfo.concat("<").concat(colType).concat(">");
}
}
final JsonArrayBuilder jab = Json.createArrayBuilder();
for (final Object obj : collection) {
jab.add(JsonSupport.convertToJson(obj));
}
return new Entry(typeInfo, jab.build());
}
// scalar types - start with special cases for numbers
if (value instanceof Integer) {
return new Entry("Integer", Json.createValue((Integer) value));
} else if (value instanceof Float) {
return new Entry("Float", Json.createValue((Float) value));
} else if (value instanceof Short) {
return new Entry("Short", Json.createValue((Short) value));
} else if (value instanceof Byte) {
return new Entry("Byte", Json.createValue((Byte) value));
} else if (value instanceof Character) {
return new Entry("Character", Json.createValue(value.toString()));
}
final String typeInfo = findType(value.getClass());
if (typeInfo != null) {
return new Entry(typeInfo, JsonSupport.convertToJson(value.toString()));
}
return new Entry(NO_TYPE_INFO, JsonSupport.convertToJson(value));
}
private static final class Entry implements Map.Entry<String, JsonValue> {
private final JsonValue value;
private final String type;
public Entry(final String type, final JsonValue value) {
this.type = type;
this.value = value;
}
@Override
public String getKey() {
return this.type;
}
@Override
public JsonValue getValue() {
return this.value;
}
@Override
public JsonValue setValue(JsonValue value) {
return null;
}
}
}