You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cz...@apache.org on 2020/04/13 08:27:00 UTC
[felix-dev] branch master updated: Add new module for JSON
configuration handling
This is an automated email from the ASF dual-hosted git repository.
cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git
The following commit(s) were added to refs/heads/master by this push:
new 0091c7d Add new module for JSON configuration handling
0091c7d is described below
commit 0091c7d86c3bd1fdfb039fac68368f256f741bcd
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Apr 13 10:26:49 2020 +0200
Add new module for JSON configuration handling
---
cm.json/pom.xml | 125 ++++
.../apache/felix/cm/json/ConfigurationReader.java | 150 +++++
.../felix/cm/json/ConfigurationResource.java | 51 ++
.../apache/felix/cm/json/ConfigurationWriter.java | 77 +++
.../org/apache/felix/cm/json/Configurations.java | 123 ++++
.../cm/json/impl/ConfigurationReaderImpl.java | 310 ++++++++++
.../cm/json/impl/ConfigurationWriterImpl.java | 111 ++++
.../org/apache/felix/cm/json/impl/JsonSupport.java | 305 ++++++++++
.../felix/cm/json/impl/OrderedDictionary.java | 145 +++++
.../apache/felix/cm/json/impl/TypeConverter.java | 270 +++++++++
.../org/apache/felix/cm/json/package-info.java | 22 +
.../cm/json/impl/ConfigurationReaderImplTest.java | 92 +++
.../apache/felix/cm/json/impl/JsonSupportTest.java | 193 ++++++
.../felix/cm/json/impl/TypeConverterTest.java | 650 +++++++++++++++++++++
cm.json/src/test/resources/configs/bundle.json | 8 +
cm.json/src/test/resources/configs/resource.json | 10 +
cm.json/src/test/resources/configs/single.json | 3 +
17 files changed, 2645 insertions(+)
diff --git a/cm.json/pom.xml b/cm.json/pom.xml
new file mode 100644
index 0000000..9d75a1f
--- /dev/null
+++ b/cm.json/pom.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix-parent</artifactId>
+ <version>6</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>org.apache.felix.cm.json</artifactId>
+ <packaging>bundle</packaging>
+
+ <name>Apache Felix Configuration Json</name>
+ <description>
+ Support for OSGi configurations specified in JSON documents.
+ </description>
+ <version>0.0.1-SNAPSHOT</version>
+
+ <scm>
+ <connection>scm:git:https://github.com/apache/felix-dev/tree/master/cm.json</connection>
+ <developerConnection>scm:git:https://github.com/apache/felix-dev/tree/master/cm.json</developerConnection>
+ <url>https://gitbox.apache.org/repos/asf?p=felix-dev.git;f=cm.json</url>
+ </scm>
+
+ <properties>
+ <felix.java.version>8</felix.java.version>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>3.5.0</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Category>osgi</Bundle-Category>
+ <Bundle-SymbolicName>
+ ${project.artifactId}
+ </Bundle-SymbolicName>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes combine.children="append">
+ <!-- Exclude all JSON files -->
+ <exclude>**/*.json</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.annotation</artifactId>
+ <version>6.0.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>6.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.configurator</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.converter</artifactId>
+ <version>1.0.12</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-json_1.1_spec</artifactId>
+ <version>1.2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.util.function</artifactId>
+ <version>1.0.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.johnzon</groupId>
+ <artifactId>johnzon-core</artifactId>
+ <version>1.2.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.13</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationReader.java b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationReader.java
new file mode 100644
index 0000000..7bf5b96
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationReader.java
@@ -0,0 +1,150 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.json.JsonObject;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * A configuration reader can read configuration resources as defined in
+ * {@link https://osgi.org/specification/osgi.cmpn/7.0.0/service.configurator.html}.
+ * A reader can be obtained through a {@link Builder}.
+ */
+@ProviderType
+public interface ConfigurationReader {
+
+ /**
+ * Read a single configuration from the provided reader. The reader is not
+ * closed. {@link #getIgnoredErrors()} can be used after reading if any ignored
+ * errors occurred during processing the configuration.
+ *
+ * @return The read configuration.
+ * @throws IOException If reading fails
+ */
+ Hashtable<String, Object> readConfiguration() throws IOException;
+
+ /**
+ * Read a configuration resource from the provided reader. The reader is not
+ * closed. {@link #getIgnoredErrors()} can be used after reading if any ignored
+ * errors occurred during processing the resource.
+ *
+ * @return The read configurations.
+ * @throws IOException If reading fails
+ */
+ ConfigurationResource readConfigurationResource() throws IOException;
+
+ /**
+ * After reading a configuration (resource) this method will return a list of
+ * ignored errors.
+ *
+ * @return List of ignored errors, might be empty.
+ */
+ List<String> getIgnoredErrors();
+
+ /**
+ * Builder to create a reader. A builder can be obtained using
+ * {@link Configurations#buildReader()}
+ */
+ public interface Builder {
+
+ /**
+ * By default a configuration resource is not treated as a bundle resource.
+ * Additional rules apply for a resource outside of a bundle.
+ *
+ * @return This builder
+ */
+ Builder verifyAsBundleResource();
+
+ /**
+ * Set an identifier. If set all ignored error messages and most exceptions will
+ * contain this identifier.
+ *
+ * @param identifier An identifier for the resource
+ * @return This builder
+ */
+ Builder withIdentifier(String identifier);
+
+ /**
+ * Set the binary handler.
+ * If no binary handler is set and a binary property is encountered
+ * the reading of the configuration resource fails.
+ *
+ * @param handler The binary handler
+ * @return This builder
+ */
+ Builder withBinaryHandler(BinaryHandler handler);
+
+ /**
+ * Set the configurator property handler.
+ * If no handler is set and a configurator property is encountered
+ * it is added as is to the returned configuration dictionary.
+ *
+ * @param handler The binary handler
+ * @return This builder
+ */
+ Builder withConfiguratorPropertyHandler(ConfiguratorPropertyHandler handler);
+
+ /**
+ * Build the configuration reader for the provided reader.
+ *
+ * @param reader The reader for the JSON
+ * @return The configuration reader
+ */
+ ConfigurationReader build(Reader reader);
+
+ /**
+ * Build the configuration reader for the JSON object.
+ *
+ * @param object The JSON object
+ * @return The configuration reader
+ */
+ ConfigurationReader build(JsonObject object);
+ }
+
+ /**
+ * Handler for binary properties
+ */
+ @FunctionalInterface
+ public interface BinaryHandler {
+
+ /**
+ * Provide the actual value for a binary property
+ * @param pid The pid of the configuration
+ * @param key The key of the binary property
+ * @param value The binary property value
+ * @return The actual value or {@code null} on failure
+ */
+ String handleBinaryValue(String pid, String key, String value);
+ }
+
+ /**
+ * Handler for configurator properties
+ */
+ @FunctionalInterface
+ public interface ConfiguratorPropertyHandler {
+
+ void handleConfiguratorProperty(String pid, String key, Object value);
+ }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationResource.java b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationResource.java
new file mode 100644
index 0000000..ac151b3
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationResource.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A configuration resource holds a set of configurations
+ */
+public final class ConfigurationResource {
+
+ public static final String CONFIGURATOR_PROPERTY_PREFIX = ":configurator:";
+
+ private final Map<String, Hashtable<String, Object>> configurations = new LinkedHashMap<>();
+
+ private final Map<String, Object> properties = new LinkedHashMap<>();
+
+ /**
+ * The set of configurations mapped by identifier.
+ * @return The configurations.
+ */
+ public Map<String, Hashtable<String, Object>> getConfigurations() {
+ return this.configurations;
+ }
+
+ /**
+ * The properties of the resource
+ * @return A map pf properties
+ */
+ public Map<String, Object> getProperties() {
+ return properties;
+ }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationWriter.java b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationWriter.java
new file mode 100644
index 0000000..0128a88
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationWriter.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Dictionary;
+
+import javax.json.stream.JsonGenerator;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * A configuration writer can write configuration resources as defined in
+ * {@link https://osgi.org/specification/osgi.cmpn/7.0.0/service.configurator.html}.
+ * A writer can be obtained through a {@link Builder}.
+ */
+@ProviderType
+public interface ConfigurationWriter {
+
+ /**
+ * Write a single configuration to the provided writer. The writer is not
+ * closed.
+ *
+ * @param properties The configuration
+ * @throws IOException If writing fails
+ */
+ void writeConfiguration(Dictionary<String, Object> properties) throws IOException;
+
+ /**
+ * Write a configuration resource to the provided writer. The writer is not
+ * closed.
+ *
+ * @param resource The configuration resource
+ * @throws IOException If writing fails
+ */
+ void writeConfigurationResource(ConfigurationResource resource) throws IOException;
+
+ /**
+ * Builder to create a writer. A builder can be obtained using
+ * {@link Configurations#buildWriter()}
+ */
+ public interface Builder {
+
+ /**
+ * Build the configuration writer for the provided writer.
+ *
+ * @param writer The writer for the JSON
+ * @return The configuration writer
+ */
+ ConfigurationWriter build(Writer writer);
+
+ /**
+ * Build the configuration writer for the provided JSON generator.
+ *
+ * @param generator The JSON generator
+ * @return The configuration writer
+ */
+ ConfigurationWriter build(JsonGenerator generator);
+ }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/Configurations.java b/cm.json/src/main/java/org/apache/felix/cm/json/Configurations.java
new file mode 100644
index 0000000..8bdd1bd
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/Configurations.java
@@ -0,0 +1,123 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Hashtable;
+
+import javax.json.JsonValue;
+
+import org.apache.felix.cm.json.impl.ConfigurationReaderImpl;
+import org.apache.felix.cm.json.impl.ConfigurationWriterImpl;
+import org.apache.felix.cm.json.impl.JsonSupport;
+import org.apache.felix.cm.json.impl.OrderedDictionary;
+
+/**
+ * Factory class for JSON and configuration support
+ */
+public class Configurations {
+
+
+ private Configurations() {
+ // Do not instantiate this factory class
+ }
+
+ /**
+ * Get a builder to create a configuration reader
+ *
+ * @return A configuration reader builder
+ */
+ public static ConfigurationReader.Builder buildReader() {
+ return new ConfigurationReaderImpl();
+ }
+
+ /**
+ * Get a builder to create a configuration writer
+ *
+ * @return A configuration writer builder
+ */
+ public static ConfigurationWriter.Builder buildWriter() {
+ return new ConfigurationWriterImpl();
+ }
+
+ /**
+ * Create a reader handling comments in JSON. The reader tries to leave the original
+ * structure of the document untouched, meaning comments will be replace with spaces.
+ * Closing this reader will close the underlying reader.
+ *
+ * @param reader Reader for the JSON
+ * @return Reader for the pure JSON
+ * @throws IOException If reading fails.
+ */
+ public static Reader jsonCommentAwareReader(final Reader reader) throws IOException {
+ return JsonSupport.createCommentRemovingReader(reader);
+ }
+
+ /**
+ * Create a new map for configuration properties The returned map keeps the
+ * order of properties added.
+ *
+ * @return A new map
+ */
+ public static Hashtable<String, Object> newConfiguration() {
+ return new OrderedDictionary();
+ }
+
+ /**
+ * Convert a json value into an object. The resulting object is
+ * <ul>
+ * <li>{@code null} : if the provided value is {@code null} or
+ * {@code ValueType#NULL}
+ * <li>A Boolean : if the json value is a boolean
+ * <li>A Long : if the json value contains an integral number
+ * <li>A Double : if the json value contains a non integral number
+ * <li>A String : for any other provided non array value
+ * <li>A Boolean[] : if the provided value is an array of booleans
+ * <li>A Long[] : if the provided value is an array of integral numbers
+ * <li>A Double[] : if the provided value is an array of non integral numbers
+ * <li>A String[] : for any other provided array value
+ * </ul>
+ *
+ * @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) {
+ return JsonSupport.convertToObject(value);
+ }
+
+ /**
+ * Convert an object to a JSON value. The resulting value is:
+ * <ul>
+ * <li>{@code JsonValue.NULL} : if the provided value is {@code null}
+ * <li>{@code JsonValue.TRUE} or {@code JsonValue.FALSE} : if the provided value is Boolean.
+ * <li>A JSON value of type {@code JsonValue.ValueType#NUMBER} : if the value is a Integer, Long, Float, Double, Byte, Short
+ * <li>{@code JsonValue.ValueType#OBJECT} : if the value is a map
+ * <li>{@code JsonValue.ValueType#ARRAY} : if the value is an array or a collection
+ * <li>{@code JsonValue.ValueType#STRING} : otherwise, calling {@code toString()} on the value.
+ * <li>
+ * </ul>
+ * @param value The object
+ * @return The JSON value.
+ */
+ public static JsonValue convertToJsonValue(final Object value) {
+ return JsonSupport.convertToJson(value);
+ }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationReaderImpl.java b/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationReaderImpl.java
new file mode 100644
index 0000000..b3b73b2
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationReaderImpl.java
@@ -0,0 +1,310 @@
+/*
+ * 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.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+import javax.json.JsonValue.ValueType;
+
+import org.apache.felix.cm.json.ConfigurationReader;
+import org.apache.felix.cm.json.ConfigurationResource;
+import org.apache.felix.cm.json.Configurations;
+import org.osgi.service.configurator.ConfiguratorConstants;
+import org.osgi.util.converter.Converters;
+
+public class ConfigurationReaderImpl
+ implements ConfigurationReader, ConfigurationReader.Builder {
+
+ private boolean closed = false;
+
+ private Reader reader;
+
+ private JsonObject jsonObject;
+
+ private boolean verifyAsBundleResource = false;
+
+ private String identifier;
+
+ private final List<String> errors = new ArrayList<>();
+
+ private BinaryHandler binaryHandler;
+
+ private ConfiguratorPropertyHandler propertyHandler;
+
+ @Override
+ public Builder verifyAsBundleResource() {
+ this.verifyAsBundleResource = true;
+ return this;
+ }
+
+ @Override
+ public Builder withIdentifier(final String value) {
+ this.identifier = value;
+ return this;
+ }
+
+ @Override
+ public Builder withBinaryHandler(final BinaryHandler handler) {
+ this.binaryHandler = handler;
+ return this;
+ }
+
+ @Override
+ public Builder withConfiguratorPropertyHandler(final ConfiguratorPropertyHandler handler) {
+ this.propertyHandler = handler;
+ return this;
+ }
+
+ @Override
+ public ConfigurationReader build(final Reader reader) {
+ this.reader = reader;
+ return this;
+ }
+
+ @Override
+ public ConfigurationReader build(final JsonObject object) {
+ this.jsonObject = object;
+ return this;
+ }
+
+ @Override
+ public List<String> getIgnoredErrors() {
+ return this.errors;
+ }
+
+ private void checkClosed() throws IOException {
+ if (this.closed) {
+ throwIOException("Reader already closed");
+ }
+ closed = true;
+ }
+
+ @Override
+ public Hashtable<String, Object> readConfiguration() throws IOException {
+ checkClosed();
+ if (this.reader != null) {
+ this.jsonObject = JsonSupport.parseJson(this.identifier, this.reader);
+ }
+ return readSingleConfiguration("<configuration>", this.jsonObject);
+ }
+
+ @Override
+ public ConfigurationResource readConfigurationResource() throws IOException {
+ checkClosed();
+ if (this.reader != null) {
+ this.jsonObject = JsonSupport.parseJson(this.identifier, this.reader);
+ }
+ verifyJsonResource();
+ final ConfigurationResource resource = new ConfigurationResource();
+
+ for (final Map.Entry<String, JsonValue> entry : this.jsonObject.entrySet()) {
+ if (entry.getKey().startsWith(ConfigurationResource.CONFIGURATOR_PROPERTY_PREFIX)) {
+ // internal property
+ resource.getProperties().put(entry.getKey(), JsonSupport.convertToObject(entry.getValue()));
+ } else if (entry.getValue().getValueType() != ValueType.OBJECT) {
+ addError("Ignoring property (not a configuration) : ".concat(entry.getKey()));
+ resource.getProperties().put(entry.getKey(), JsonSupport.convertToObject(entry.getValue()));
+ } else {
+ final Hashtable<String, Object> properties = readSingleConfiguration(entry.getKey(),
+ entry.getValue().asJsonObject());
+ if (properties != null) {
+ resource.getConfigurations().put(entry.getKey(), properties);
+ }
+ }
+ }
+ return resource;
+ }
+
+ private void addError(final String msg) {
+ if (this.identifier == null) {
+ this.errors.add(msg);
+ } else {
+ this.errors.add(identifier.concat(" : ").concat(msg));
+ }
+ }
+
+ private void throwIOException(final String msg) throws IOException {
+ if (this.identifier == null) {
+ throw new IOException(msg);
+ }
+ throw new IOException(this.identifier.concat(" : ").concat(msg));
+ }
+
+ /**
+ * Verify the JSON according to the rules
+ *
+ * @param root The JSON root object.
+ */
+ private void verifyJsonResource() throws IOException {
+ final Object version = JsonSupport
+ .convertToObject(this.jsonObject.get(ConfiguratorConstants.PROPERTY_RESOURCE_VERSION));
+ if (version != null) {
+ final int v = Converters.standardConverter().convert(version).defaultValue(-1).to(Integer.class);
+ if (v == -1) {
+ throwIOException("Invalid resource version information : ".concat(version.toString()));
+ }
+ // we only support version 1
+ if (v != 1) {
+ throwIOException("Unknown resource version : ".concat(version.toString()));
+ }
+ }
+ if (!verifyAsBundleResource) {
+ // if this is not a bundle resource
+ // then version and symbolic name must be set
+ final Object rsrcVersion = JsonSupport
+ .convertToObject(this.jsonObject.get(ConfiguratorConstants.PROPERTY_VERSION));
+ if (rsrcVersion == null) {
+ throwIOException("Missing version information");
+ }
+ if (!(rsrcVersion instanceof String)) {
+ throwIOException("Invalid version information : ".concat(rsrcVersion.toString()));
+ }
+ final Object rsrcName = JsonSupport
+ .convertToObject(this.jsonObject.get(ConfiguratorConstants.PROPERTY_SYMBOLIC_NAME));
+ if (rsrcName == null) {
+ throwIOException("Missing symbolic name information");
+ }
+ if (!(rsrcName instanceof String)) {
+ throwIOException("Invalid symbolic name information : ".concat(rsrcVersion.toString()));
+ }
+ }
+ }
+
+ public static final class KeyInfo {
+
+ public final String propertyKey;
+
+ public final String typeInfo;
+
+ public final boolean isInternal;
+
+ public final boolean isBinary;
+
+ public KeyInfo(final String mapKey) {
+ this.isInternal = mapKey.startsWith(ConfigurationResource.CONFIGURATOR_PROPERTY_PREFIX);
+ String key = mapKey;
+ if (isInternal) {
+ key = key.substring(ConfigurationResource.CONFIGURATOR_PROPERTY_PREFIX.length());
+ }
+ final int pos = key.indexOf(':');
+ String typeInfo = null;
+ if (pos != -1) {
+ typeInfo = key.substring(pos + 1);
+ key = key.substring(0, pos);
+ }
+ this.propertyKey = key;
+ this.typeInfo = typeInfo;
+ this.isBinary = TypeConverter.TYPE_BINARY.equals(typeInfo) || TypeConverter.TYPE_BINARIES.equals(typeInfo);
+ }
+ }
+
+ /**
+ * Read a single configuration
+ *
+ * @param pid The configuration pid
+ * @param propertyMap The configuration map
+ * @return A valid configuration dictionary or {@code null}
+ * @throws IOException If reading fails
+ */
+ private Hashtable<String, Object> readSingleConfiguration(final String pid,
+ final JsonObject propertyMap) throws IOException {
+
+ final Hashtable<String, Object> properties = Configurations.newConfiguration();
+ boolean valid = true;
+ for (final Map.Entry<String, JsonValue> propEntry : propertyMap.entrySet()) {
+ final String mapKey = propEntry.getKey();
+
+ final KeyInfo keyInfo = new KeyInfo(mapKey);
+
+ if ( keyInfo.isBinary ) {
+ if ( !this.verifyAsBundleResource) {
+ throwIOException("PID ".concat(pid)
+ .concat(" : Properties of type binary not allowed for non bundle resource : ")
+ .concat(keyInfo.propertyKey));
+ }
+ if ( this.binaryHandler == null ) {
+ throwIOException("PID ".concat(pid)
+ .concat(" : No handler configured for binary property : ")
+ .concat(keyInfo.propertyKey));
+ }
+ if ( keyInfo.isInternal ) {
+ throwIOException("PID ".concat(pid)
+ .concat(" : Binary values are not allowed for configurator properties : ")
+ .concat(keyInfo.propertyKey));
+ }
+ }
+
+ if ( keyInfo.isInternal ) {
+ final Object value = JsonSupport.convertToObject(propEntry.getValue());
+ if ( propertyHandler == null ) {
+ properties.put(keyInfo.propertyKey, value);
+ } else {
+ propertyHandler.handleConfiguratorProperty(pid, keyInfo.propertyKey, value);
+ }
+ } else {
+ // convert value
+ Object convertedVal = TypeConverter.convertObjectToType(propEntry.getValue(), keyInfo.typeInfo);
+
+ if ( convertedVal == TypeConverter.CONVERSION_FAILED ) {
+ final String msg = "PID ".concat(pid).concat(" : Invalid value/type for configuration : ")
+ .concat(mapKey).concat(" : ").concat(propEntry.getValue().toString());
+ if (!this.verifyAsBundleResource) {
+ throwIOException(msg);
+ }
+ addError(msg);
+ valid = false;
+ break;
+ }
+
+ // special handling for binary and binary[]
+ if (keyInfo.isBinary) {
+ // String or String[] ?
+ if ( convertedVal instanceof String ) {
+ final String path = (String)convertedVal;
+ convertedVal = this.binaryHandler.handleBinaryValue(pid, keyInfo.propertyKey, path);
+ } else {
+ final String[] paths = (String[])convertedVal;
+ final String[] array = new String[paths.length];
+ convertedVal = array;
+ for(int i=0;i<paths.length;i++) {
+ array[i] = this.binaryHandler.handleBinaryValue(pid, keyInfo.propertyKey, paths[i]);
+ if ( array[i] == null ) {
+ convertedVal = null;
+ }
+ }
+ }
+ }
+ // if ( convertedVal is null) this configuration is invalid
+ if ( convertedVal == null ) {
+ valid = false;
+ } else {
+ properties.put(keyInfo.propertyKey, convertedVal);
+ }
+ }
+ }
+ return valid ? properties : null;
+ }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationWriterImpl.java b/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationWriterImpl.java
new file mode 100644
index 0000000..a643e6a
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationWriterImpl.java
@@ -0,0 +1,111 @@
+/*
+ * 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.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonValue;
+import javax.json.stream.JsonGenerator;
+
+import org.apache.felix.cm.json.ConfigurationResource;
+import org.apache.felix.cm.json.ConfigurationWriter;
+
+public class ConfigurationWriterImpl
+ implements ConfigurationWriter, ConfigurationWriter.Builder {
+
+ private boolean closed = false;
+
+ private JsonGenerator generator;
+
+ private void checkClosed() throws IOException {
+ if (this.closed) {
+ throw new IOException("Writer already closed");
+ }
+ closed = true;
+ }
+
+ @Override
+ public ConfigurationWriter build(final Writer writer) {
+ this.generator = Json.createGeneratorFactory(Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true))
+ .createGenerator(new FilterWriter(writer) {
+
+ @Override
+ public void close() throws IOException {
+ // do not close provided writer, just flush
+ super.flush();
+ }
+
+ });
+ return this;
+ }
+
+ @Override
+ public ConfigurationWriter build(final JsonGenerator generator) {
+ this.generator = generator;
+ return this;
+ }
+
+ @Override
+ public void writeConfiguration(final Dictionary<String, Object> properties) throws IOException {
+ checkClosed();
+ writeConfigurationInternal(properties);
+ }
+
+ private void writeConfigurationInternal(final Dictionary<String, Object> properties) throws IOException {
+ generator.writeStartObject();
+ final Enumeration<String> e = properties.keys();
+ while (e.hasMoreElements()) {
+ final String name = e.nextElement();
+ final Object value = properties.get(name);
+
+ final Map.Entry<String, JsonValue> entry = TypeConverter.convertObjectToTypedJsonValue(value);
+ final String key;
+ if (TypeConverter.NO_TYPE_INFO.equals(entry.getKey())) {
+ key = name;
+ } else {
+ key = name.concat(":").concat(entry.getKey());
+ }
+ generator.write(key, entry.getValue());
+ }
+ generator.writeEnd();
+ }
+
+ @Override
+ public void writeConfigurationResource(final ConfigurationResource resource) throws IOException {
+ checkClosed();
+ generator.writeStartObject();
+ for (final Map.Entry<String, Object> entry : resource.getProperties().entrySet()) {
+ generator.write(entry.getKey(), JsonSupport.convertToJson(entry.getValue()));
+ }
+ for (final Map.Entry<String, Hashtable<String, Object>> entry : resource.getConfigurations().entrySet()) {
+ generator.writeKey(entry.getKey());
+ writeConfigurationInternal(entry.getValue());
+ }
+ generator.writeEnd();
+
+ }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/impl/JsonSupport.java b/cm.json/src/main/java/org/apache/felix/cm/json/impl/JsonSupport.java
new file mode 100644
index 0000000..5b72c96
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/impl/JsonSupport.java
@@ -0,0 +1,305 @@
+/*
+ * 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;
+
+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) {
+ final boolean isLong = ((JsonNumber) array.get(0)).isIntegral();
+ objArray = isLong ? new Long[array.size()] : new Double[array.size()];
+ } else if (arrayType == ValueType.STRING) {
+ objArray = new String[array.size()];
+ } else {
+ objArray = null;
+ }
+ if (objArray != null) {
+ for (int i = 0; i < array.size(); i++) {
+ objArray[i] = convertToObject(array.get(i));
+ }
+ 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 ValueType#OBJECT} 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();
+ final boolean isLong = vt == ValueType.NUMBER && ((JsonNumber) array.get(0)).isIntegral();
+ for (int i = 1; i < array.size(); i++) {
+ final ValueType ct = array.get(i).getValueType();
+ boolean isSame = false;
+ if (ct == vt) {
+ isSame = true;
+ if (vt == ValueType.NUMBER) {
+ if (isLong != ((JsonNumber) array.get(i)).isIntegral()) {
+ isSame = false;
+ }
+ }
+ } else if (vt == ValueType.TRUE && ct == ValueType.FALSE) {
+ isSame = true;
+ } else if (vt == ValueType.FALSE && ct == ValueType.TRUE) {
+ isSame = true;
+ }
+ if (!isSame) {
+ return ValueType.OBJECT;
+ }
+ }
+ 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();
+ }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/impl/OrderedDictionary.java b/cm.json/src/main/java/org/apache/felix/cm/json/impl/OrderedDictionary.java
new file mode 100644
index 0000000..5c3f8b4
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/impl/OrderedDictionary.java
@@ -0,0 +1,145 @@
+/*
+ * 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.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A dictionary implementation with predictable iteration order.
+ *
+ * Actually this class is a simple adapter from the Dictionary interface
+ * to a synchronized LinkedHashMap
+ */
+public class OrderedDictionary extends Hashtable<String, Object> implements Serializable {
+
+ private static final long serialVersionUID = -525111601546803041L;
+
+ private static class EnumarationImpl<E> implements Enumeration<E> {
+ private final Iterator<E> iterator;
+
+ public EnumarationImpl(Iterator<E> iterator) {
+ this.iterator = iterator;
+ }
+
+ @Override
+ public boolean hasMoreElements() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public E nextElement() {
+ return iterator.next();
+ }
+ }
+
+ private Map<String, Object> map = Collections.synchronizedMap(new LinkedHashMap<String, Object>());
+
+ @Override
+ public int size() {
+ return map.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return map.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return map.containsValue(value);
+ }
+
+ @Override
+ public Enumeration<String> keys() {
+ return new EnumarationImpl<>(map.keySet().iterator());
+ }
+
+ @Override
+ public Enumeration<Object> elements() {
+ return new EnumarationImpl<>(map.values().iterator());
+ }
+
+ @Override
+ public Object get(Object key) {
+ return map.get(key);
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ // Make sure the value is not null
+ if (value == null) {
+ throw new NullPointerException();
+ }
+
+ return map.put(key, value);
+ }
+
+ @Override
+ public Object remove(Object key) {
+ return map.remove(key);
+ }
+
+ @Override
+ public void putAll(Map<? extends String, ? extends Object> m) {
+ map.putAll(m);
+ }
+
+ @Override
+ public void clear() {
+ map.clear();
+ }
+
+ @Override
+ public Set<String> keySet() {
+ return map.keySet();
+ }
+
+ @Override
+ public Collection<Object> values() {
+ return map.values();
+ }
+
+ @Override
+ public Set<java.util.Map.Entry<String, Object>> entrySet() {
+ return map.entrySet();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return map.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return map.hashCode();
+ }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/impl/TypeConverter.java b/cm.json/src/main/java/org/apache/felix/cm/json/impl/TypeConverter.java
new file mode 100644
index 0000000..1769ae9
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/impl/TypeConverter.java
@@ -0,0 +1,270 @@
+/*
+ * 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("int", Integer.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("char", Character.class);
+ TYPE_MAP.put("Character", 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;
+ }
+ }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/package-info.java b/cm.json/src/main/java/org/apache/felix/cm/json/package-info.java
new file mode 100644
index 0000000..4f9f76a
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+@Version("1.0.0")
+package org.apache.felix.cm.json;
+
+import org.osgi.annotation.versioning.Version;
\ No newline at end of file
diff --git a/cm.json/src/test/java/org/apache/felix/cm/json/impl/ConfigurationReaderImplTest.java b/cm.json/src/test/java/org/apache/felix/cm/json/impl/ConfigurationReaderImplTest.java
new file mode 100644
index 0000000..606db74
--- /dev/null
+++ b/cm.json/src/test/java/org/apache/felix/cm/json/impl/ConfigurationReaderImplTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class ConfigurationReaderImplTest {
+
+ @Test
+ public void testReadSingleConfiguration() throws IOException {
+ final ConfigurationReaderImpl cfgReader = new ConfigurationReaderImpl();
+ final Dictionary<String, Object> config;
+ try (final InputStream jsonStream = this.getClass().getResourceAsStream("/configs/single.json");
+ final Reader jsonReader = new InputStreamReader(jsonStream, StandardCharsets.UTF_8)) {
+ config = cfgReader.build(jsonReader).readConfiguration();
+ }
+ assertEquals(1, config.size());
+ assertEquals("Hello World", config.get("text"));
+ }
+
+ @Test
+ public void testReadConfigurationResource() throws IOException {
+ final ConfigurationReaderImpl cfgReader = new ConfigurationReaderImpl();
+ final Map<String, Hashtable<String, Object>> configs;
+ try (final InputStream jsonStream = this.getClass().getResourceAsStream("/configs/resource.json");
+ final Reader jsonReader = new InputStreamReader(jsonStream, StandardCharsets.UTF_8)) {
+ configs = cfgReader.build(jsonReader).readConfigurationResource().getConfigurations();
+ }
+ assertEquals(2, configs.size());
+ assertNotNull(configs.get("config.a"));
+ assertNotNull(configs.get("config.b"));
+ assertEquals("Hello World", configs.get("config.a").get("text"));
+ assertEquals(8080, configs.get("config.b").get("port"));
+ }
+
+ @Test
+ public void testReadConfigurationResourceMissingProperties() throws IOException {
+ final ConfigurationReaderImpl cfgReader = new ConfigurationReaderImpl();
+ try (final InputStream jsonStream = this.getClass().getResourceAsStream("/configs/bundle.json");
+ final Reader jsonReader = new InputStreamReader(jsonStream, StandardCharsets.UTF_8)) {
+ cfgReader.build(jsonReader).readConfigurationResource();
+ fail();
+ } catch (final IOException ioe) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testReadBundleConfigurationResource() throws IOException {
+ final ConfigurationReaderImpl cfgReader = new ConfigurationReaderImpl();
+ cfgReader.verifyAsBundleResource();
+ final Map<String, Hashtable<String, Object>> configs;
+ try (final InputStream jsonStream = this.getClass().getResourceAsStream("/configs/bundle.json");
+ final Reader jsonReader = new InputStreamReader(jsonStream, StandardCharsets.UTF_8)) {
+ configs = cfgReader.build(jsonReader).readConfigurationResource().getConfigurations();
+ }
+ assertEquals(2, configs.size());
+ assertNotNull(configs.get("config.a"));
+ assertNotNull(configs.get("config.b"));
+ assertEquals("Hello World", configs.get("config.a").get("text"));
+ assertEquals(8080, configs.get("config.b").get("port"));
+ }
+}
diff --git a/cm.json/src/test/java/org/apache/felix/cm/json/impl/JsonSupportTest.java b/cm.json/src/test/java/org/apache/felix/cm/json/impl/JsonSupportTest.java
new file mode 100644
index 0000000..71a192d
--- /dev/null
+++ b/cm.json/src/test/java/org/apache/felix/cm/json/impl/JsonSupportTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+import org.apache.felix.cm.json.Configurations;
+import org.junit.Test;
+
+/**
+ * This class tests the {@code JsonSupport} class, however it calls the api
+ * through the public {@code Configurations} api.
+ */
+public class JsonSupportTest {
+
+ @Test
+ public void testConvertNullToObject() {
+ assertNull(Configurations.convertToObject(null));
+ assertNull(Configurations.convertToObject(JsonValue.NULL));
+ }
+
+ @Test
+ public void testConvertBooleanToObject() {
+ assertEquals(Boolean.TRUE, Configurations.convertToObject(JsonValue.TRUE));
+ assertEquals(Boolean.FALSE, Configurations.convertToObject(JsonValue.FALSE));
+ }
+
+ @Test
+ public void testConvertNumberToObject() {
+ assertEquals(7L, Configurations.convertToObject(Json.createValue(7)));
+ assertEquals(7L, Configurations.convertToObject(Json.createValue((long) 7)));
+ assertEquals(3.1d, Configurations.convertToObject(Json.createValue(3.1)));
+ }
+
+ public void testConvertStringToObject() {
+ assertEquals("hello world", Configurations.convertToObject(Json.createValue("hello world")));
+ }
+
+ public void testConvertObjectToObject() {
+ final JsonObjectBuilder builder = Json.createObjectBuilder();
+ builder.add("hello", "world");
+
+ assertEquals("{\"hello\":\"world\"}", Configurations.convertToObject(builder.build()));
+ }
+
+ @Test
+ public void testConvertBooleanArrayToObject() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add(true);
+ builder.add(false);
+
+ assertArrayEquals(new Boolean[] { true, false }, (Boolean[]) Configurations.convertToObject(builder.build()));
+ }
+
+ @Test
+ public void testConvertNumberArrayToObject() {
+ final JsonArrayBuilder lBuilder = Json.createArrayBuilder();
+ lBuilder.add(5);
+ lBuilder.add(3);
+
+ final JsonArrayBuilder dBuilder = Json.createArrayBuilder();
+ dBuilder.add(5.7);
+ dBuilder.add(3.7);
+
+ final JsonArrayBuilder mBuilder = Json.createArrayBuilder();
+ mBuilder.add(5.7);
+ mBuilder.add(3);
+
+ assertArrayEquals(new Long[] { 5L, 3L }, (Long[]) Configurations.convertToObject(lBuilder.build()));
+ assertArrayEquals(new Double[] { 5.7, 3.7 }, (Double[]) Configurations.convertToObject(dBuilder.build()));
+ assertEquals("[5.7,3]", Configurations.convertToObject(mBuilder.build()));
+ }
+
+ public void testConvertStringArrayToObject() {
+ final JsonArrayBuilder sBuilder = Json.createArrayBuilder();
+ sBuilder.add("hello");
+ sBuilder.add("world");
+
+ assertArrayEquals(new String[] { "hello", "world" },
+ (String[]) Configurations.convertToObject(sBuilder.build()));
+
+ final JsonArrayBuilder mBuilder = Json.createArrayBuilder();
+ mBuilder.add("hello");
+ mBuilder.add("3");
+
+ assertArrayEquals(new String[] { "hello", "3" }, (String[]) Configurations.convertToObject(mBuilder.build()));
+ }
+
+ public void testConvertObjectArrayToObject() {
+ final JsonArrayBuilder sBuilder = Json.createArrayBuilder();
+ final JsonObjectBuilder o1 = Json.createObjectBuilder();
+ o1.add("a", "1");
+ final JsonObjectBuilder o2 = Json.createObjectBuilder();
+ o2.add("b", 2);
+ sBuilder.add(o1);
+ sBuilder.add(o2);
+
+ assertArrayEquals(new String[] { "{\"a\":\"1\"}", "{\"b\":2}" },
+ (String[]) Configurations.convertToObject(sBuilder.build()));
+ }
+
+ private String parse(final String input) throws IOException {
+ try ( final Reader reader = Configurations.jsonCommentAwareReader(new StringReader(input))) {
+ final StringWriter w = new StringWriter();
+ int l = 0;
+ char[] buf = new char[4096];
+ while ( (l = reader.read(buf)) > 0 ) {
+ w.write(buf, 0, l);
+ }
+ w.flush();
+
+ return w.toString();
+ }
+ }
+
+ @Test public void testLineCommentTop() throws IOException {
+ final String input = "// Some comment\n" +
+ "{\n"
+ + " \"a\" : 1,\n"
+ + " \"b\" : 2\n"
+ + "}\n";
+
+ assertEquals("\n" +
+ "{\n"
+ + " \"a\" : 1,\n"
+ + " \"b\" : 2\n"
+ + "}\n", parse(input));
+ }
+
+ @Test public void testLineComment() throws IOException {
+ final String input = "{\n"
+ + " \"a\" : 1,\n"
+ + " // another comment\n"
+ + " \"b\" : 2\n"
+ + "}\n";
+
+ assertEquals("{\n"
+ + " \"a\" : 1,\n"
+ + " \n"
+ + " \"b\" : 2\n"
+ + "}\n", parse(input));
+ }
+
+ @Test public void testSeveralComments() throws IOException {
+ final String input = "// Some comment\n" +
+ "{\n"
+ + " \"a\" : 1,\n"
+ + " // another comment\n"
+ + " /** And more\n"
+ + " * comments\n"
+ + " */\n"
+ + " \"b\" : 2\n"
+ + "}\n";
+
+ assertEquals("\n" +
+ "{\n"
+ + " \"a\" : 1,\n"
+ + " \n"
+ + " \n"
+ + "\n"
+ + "\n"
+ + " \"b\" : 2\n"
+ + "}\n", parse(input));
+ }
+}
diff --git a/cm.json/src/test/java/org/apache/felix/cm/json/impl/TypeConverterTest.java b/cm.json/src/test/java/org/apache/felix/cm/json/impl/TypeConverterTest.java
new file mode 100644
index 0000000..787d21d
--- /dev/null
+++ b/cm.json/src/test/java/org/apache/felix/cm/json/impl/TypeConverterTest.java
@@ -0,0 +1,650 @@
+/*
+ * 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+import org.junit.Test;
+
+public class TypeConverterTest {
+
+ @Test
+ public void testInvalidTypeInf() {
+ assertEquals(TypeConverter.CONVERSION_FAILED, TypeConverter.convertObjectToType(null, "foo"));
+ }
+
+ @Test
+ public void testConvertNullToObject() {
+ assertNull(TypeConverter.convertObjectToType(null, null));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, null));
+
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "String"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "int"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Integer"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "long"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Long"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "float"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Float"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "double"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Double"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "byte"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Byte"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "short"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Short"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "char"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Character"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "boolean"));
+ assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Boolean"));
+
+ assertArrayEquals(new String[0], (String[]) TypeConverter.convertObjectToType(JsonValue.NULL, "String[]"));
+ assertArrayEquals(new int[0], (int[]) TypeConverter.convertObjectToType(JsonValue.NULL, "int[]"));
+ assertArrayEquals(new Integer[0], (Integer[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Integer[]"));
+ assertArrayEquals(new long[0], (long[]) TypeConverter.convertObjectToType(JsonValue.NULL, "long[]"));
+ assertArrayEquals(new Long[0], (Long[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Long[]"));
+ assertArrayEquals(new float[0], (float[]) TypeConverter.convertObjectToType(JsonValue.NULL, "float[]"), 0.1f);
+ assertArrayEquals(new Float[0], (Float[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Float[]"));
+ assertArrayEquals(new double[0], (double[]) TypeConverter.convertObjectToType(JsonValue.NULL, "double[]"), 0.1);
+ assertArrayEquals(new Double[0], (Double[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Double[]"));
+ assertArrayEquals(new byte[0], (byte[]) TypeConverter.convertObjectToType(JsonValue.NULL, "byte[]"));
+ assertArrayEquals(new Byte[0], (Byte[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Byte[]"));
+ assertArrayEquals(new short[0], (short[]) TypeConverter.convertObjectToType(JsonValue.NULL, "short[]"));
+ assertArrayEquals(new Short[0], (Short[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Short[]"));
+ assertArrayEquals(new char[0], (char[]) TypeConverter.convertObjectToType(JsonValue.NULL, "char[]"));
+ assertArrayEquals(new Character[0],
+ (Character[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Character[]"));
+ assertArrayEquals(new boolean[0], (boolean[]) TypeConverter.convertObjectToType(JsonValue.NULL, "boolean[]"));
+ assertArrayEquals(new Boolean[0], (Boolean[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Boolean[]"));
+
+ assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection")).isEmpty());
+ assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<String>")).isEmpty());
+ assertTrue(
+ ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Integer>")).isEmpty());
+ assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Long>")).isEmpty());
+ assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Float>")).isEmpty());
+ assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Double>")).isEmpty());
+ assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Byte>")).isEmpty());
+ assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Short>")).isEmpty());
+ assertTrue(
+ ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Character>")).isEmpty());
+ assertTrue(
+ ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Boolean>")).isEmpty());
+ }
+
+ @Test
+ public void testConvertBooleanToObjectNoTypeInfo() {
+ assertEquals(Boolean.TRUE, TypeConverter.convertObjectToType(JsonValue.TRUE, null));
+ assertEquals(Boolean.FALSE, TypeConverter.convertObjectToType(JsonValue.FALSE, null));
+ }
+
+ @Test
+ public void testConvertNumberToObjectNoTypeInfo() {
+ assertEquals(7L, TypeConverter.convertObjectToType(Json.createValue(7), null));
+ assertEquals(7L, TypeConverter.convertObjectToType(Json.createValue((long) 7), null));
+ assertEquals(3.1, TypeConverter.convertObjectToType(Json.createValue(3.1), null));
+ }
+
+ public void testConvertStringToObjectNoTypeInfo() {
+ assertEquals("hello world", TypeConverter.convertObjectToType(Json.createValue("hello world"), null));
+ }
+
+ public void testConvertObjectToObjectNoTypeInfo() {
+ final JsonObjectBuilder builder = Json.createObjectBuilder();
+ builder.add("hello", "world");
+
+ assertEquals("{\"hello\":\"world\"}", TypeConverter.convertObjectToType(builder.build(), null));
+ }
+
+ @Test
+ public void testConvertBooleanArrayToObjectNoTypeInfo() {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+ builder.add(true);
+ builder.add(false);
+
+ assertArrayEquals(new Boolean[] { true, false },
+ (Boolean[]) TypeConverter.convertObjectToType(builder.build(), null));
+ }
+
+ @Test
+ public void testConvertNumberArrayToObjectNoTypeInfo() {
+ final JsonArrayBuilder lBuilder = Json.createArrayBuilder();
+ lBuilder.add(5);
+ lBuilder.add(3);
+
+ final JsonArrayBuilder dBuilder = Json.createArrayBuilder();
+ dBuilder.add(5.7);
+ dBuilder.add(3.7);
+
+ final JsonArrayBuilder mBuilder = Json.createArrayBuilder();
+ mBuilder.add(5.7);
+ mBuilder.add(3);
+
+ assertArrayEquals(new Long[] { 5L, 3L }, (Long[]) TypeConverter.convertObjectToType(lBuilder.build(), null));
+ assertArrayEquals(new Double[] { 5.7, 3.7 },
+ (Double[]) TypeConverter.convertObjectToType(dBuilder.build(), null));
+ assertEquals("[5.7,3]", TypeConverter.convertObjectToType(mBuilder.build(), null));
+ }
+
+ public void testConvertStringArrayToObjectNoTypeInfo() {
+ final JsonArrayBuilder sBuilder = Json.createArrayBuilder();
+ sBuilder.add("hello");
+ sBuilder.add("world");
+
+ assertArrayEquals(new String[] { "hello", "world" },
+ (String[]) TypeConverter.convertObjectToType(sBuilder.build(), null));
+
+ final JsonArrayBuilder mBuilder = Json.createArrayBuilder();
+ mBuilder.add("hello");
+ mBuilder.add("3");
+
+ assertArrayEquals(new String[] { "hello", "3" }, (String[]) TypeConverter.convertObjectToType(mBuilder.build(), null));
+ }
+
+ public void testConvertObjectArrayToObjectNoTypeInfo() {
+ final JsonArrayBuilder sBuilder = Json.createArrayBuilder();
+ final JsonObjectBuilder o1 = Json.createObjectBuilder();
+ o1.add("a", "1");
+ final JsonObjectBuilder o2 = Json.createObjectBuilder();
+ o2.add("b", 2);
+ sBuilder.add(o1);
+ sBuilder.add(o2);
+
+ assertArrayEquals(new String[] { "{\"a\":\"1\"}", "{\"b\":2}" },
+ (String[]) TypeConverter.convertObjectToType(sBuilder.build(), null));
+ }
+
+ @Test
+ public void testConvertStringToObjectWithType() {
+ assertEquals("hello", TypeConverter.convertObjectToType(Json.createValue("hello"), "String"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "int"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Integer"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "long"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Long"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "float"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Float"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "double"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Double"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "byte"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Byte"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "short"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Short"));
+ assertEquals('h', TypeConverter.convertObjectToType(Json.createValue("hello"), "char"));
+ assertEquals('h', TypeConverter.convertObjectToType(Json.createValue("hello"), "Character"));
+ assertEquals(false, TypeConverter.convertObjectToType(Json.createValue("hello"), "boolean"));
+ assertEquals(false, TypeConverter.convertObjectToType(Json.createValue("hello"), "Boolean"));
+
+ assertArrayEquals(new String[] { "hello" },
+ (String[]) TypeConverter.convertObjectToType(Json.createValue("hello"), "String[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "int[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Integer[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "long[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Long[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "float[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Float[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "double[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Double[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "byte[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Byte[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "short[]"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Short[]"));
+ assertArrayEquals(new char[] { 'h', 'e', 'l', 'l', 'o' },
+ (char[]) TypeConverter.convertObjectToType(Json.createValue("hello"), "char[]"));
+ assertArrayEquals(new Character[] { 'h', 'e', 'l', 'l', 'o' },
+ (Character[]) TypeConverter.convertObjectToType(Json.createValue("hello"), "Character[]"));
+ assertArrayEquals(new boolean[] { false },
+ (boolean[]) TypeConverter.convertObjectToType(Json.createValue("hello"), "boolean[]"));
+ assertArrayEquals(new Boolean[] { false },
+ (Boolean[]) TypeConverter.convertObjectToType(Json.createValue("hello"), "Boolean[]"));
+
+ assertArrayEquals(Collections.singletonList("hello").toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection")).toArray());
+ assertArrayEquals(Collections.singletonList("hello").toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<String>"))
+ .toArray());
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Integer>"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Long>"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Float>"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Double>"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Byte>"));
+ assertEquals(TypeConverter.CONVERSION_FAILED,
+ TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Short>"));
+
+ assertArrayEquals(Arrays.asList('h').toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Character>"))
+ .toArray());
+ assertArrayEquals(Collections.singletonList(Boolean.FALSE).toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Boolean>"))
+ .toArray());
+ }
+
+ @Test
+ public void testConvertStringWithNumberToObjectWithType() {
+ assertEquals("1", TypeConverter.convertObjectToType(Json.createValue("1"), "String"));
+ assertEquals(1, TypeConverter.convertObjectToType(Json.createValue("1"), "int"));
+ assertEquals(1, TypeConverter.convertObjectToType(Json.createValue("1"), "Integer"));
+ assertEquals(1L, TypeConverter.convertObjectToType(Json.createValue("1"), "long"));
+ assertEquals(1L, TypeConverter.convertObjectToType(Json.createValue("1"), "Long"));
+ assertEquals(1.0f, TypeConverter.convertObjectToType(Json.createValue("1"), "float"));
+ assertEquals(1.0f, TypeConverter.convertObjectToType(Json.createValue("1"), "Float"));
+ assertEquals(1.0, TypeConverter.convertObjectToType(Json.createValue("1"), "double"));
+ assertEquals(1.0, TypeConverter.convertObjectToType(Json.createValue("1"), "Double"));
+ assertEquals((byte) 1, TypeConverter.convertObjectToType(Json.createValue("1"), "byte"));
+ assertEquals((byte) 1, TypeConverter.convertObjectToType(Json.createValue("1"), "Byte"));
+ assertEquals((short) 1, TypeConverter.convertObjectToType(Json.createValue("1"), "short"));
+ assertEquals((short) 1, TypeConverter.convertObjectToType(Json.createValue("1"), "Short"));
+ assertEquals('1', TypeConverter.convertObjectToType(Json.createValue("1"), "char"));
+ assertEquals('1', TypeConverter.convertObjectToType(Json.createValue("1"), "Character"));
+ assertEquals(false, TypeConverter.convertObjectToType(Json.createValue("1"), "boolean"));
+ assertEquals(false, TypeConverter.convertObjectToType(Json.createValue("1"), "Boolean"));
+
+ assertArrayEquals(new String[] { "1" },
+ (String[]) TypeConverter.convertObjectToType(Json.createValue("1"), "String[]"));
+ assertArrayEquals(new int[] { 1 }, (int[]) TypeConverter.convertObjectToType(Json.createValue("1"), "int[]"));
+ assertArrayEquals(new Integer[] { 1 },
+ (Integer[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Integer[]"));
+ assertArrayEquals(new long[] { 1 },
+ (long[]) TypeConverter.convertObjectToType(Json.createValue("1"), "long[]"));
+ assertArrayEquals(new Long[] { 1L },
+ (Long[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Long[]"));
+ assertArrayEquals(new float[] { 1f },
+ (float[]) TypeConverter.convertObjectToType(Json.createValue("1"), "float[]"), 0.1f);
+ assertArrayEquals(new Float[] { 1f },
+ (Float[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Float[]"));
+ assertArrayEquals(new double[] { 1 },
+ (double[]) TypeConverter.convertObjectToType(Json.createValue("1"), "double[]"), 0.1);
+ assertArrayEquals(new Double[] { 1.0 },
+ (Double[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Double[]"));
+ assertArrayEquals(new byte[] { 1 },
+ (byte[]) TypeConverter.convertObjectToType(Json.createValue("1"), "byte[]"));
+ assertArrayEquals(new Byte[] { 1 },
+ (Byte[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Byte[]"));
+ assertArrayEquals(new short[] { 1 },
+ (short[]) TypeConverter.convertObjectToType(Json.createValue("1"), "short[]"));
+ assertArrayEquals(new Short[] { 1 },
+ (Short[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Short[]"));
+ assertArrayEquals(new char[] { '1' },
+ (char[]) TypeConverter.convertObjectToType(Json.createValue("1"), "char[]"));
+ assertArrayEquals(new Character[] { '1' },
+ (Character[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Character[]"));
+ assertArrayEquals(new boolean[] { false },
+ (boolean[]) TypeConverter.convertObjectToType(Json.createValue("1"), "boolean[]"));
+ assertArrayEquals(new Boolean[] { false },
+ (Boolean[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Boolean[]"));
+
+ assertArrayEquals(Collections.singletonList("1").toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection")).toArray());
+ assertArrayEquals(Collections.singletonList("1").toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<String>"))
+ .toArray());
+ assertArrayEquals(new Integer[] { 1 },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Integer>"))
+ .toArray());
+ assertArrayEquals(new Long[] { 1L },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Long>"))
+ .toArray());
+ assertArrayEquals(new Float[] { 1f },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Float>"))
+ .toArray());
+ assertArrayEquals(new Double[] { 1.0 },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Double>"))
+ .toArray());
+ assertArrayEquals(new Byte[] { 1 },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Byte>"))
+ .toArray());
+ assertArrayEquals(new Short[] { 1 },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Short>"))
+ .toArray());
+ assertArrayEquals(Arrays.asList('1').toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Character>"))
+ .toArray());
+ assertArrayEquals(Collections.singletonList(Boolean.FALSE).toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Boolean>"))
+ .toArray());
+ }
+
+ @Test
+ public void testConvertNumberToObjectWithType() {
+ assertEquals("1", TypeConverter.convertObjectToType(Json.createValue(1), "String"));
+ assertEquals(1, TypeConverter.convertObjectToType(Json.createValue(1), "int"));
+ assertEquals(1, TypeConverter.convertObjectToType(Json.createValue(1), "Integer"));
+ assertEquals(1L, TypeConverter.convertObjectToType(Json.createValue(1), "long"));
+ assertEquals(1L, TypeConverter.convertObjectToType(Json.createValue(1), "Long"));
+ assertEquals(1.0f, TypeConverter.convertObjectToType(Json.createValue(1), "float"));
+ assertEquals(1.0f, TypeConverter.convertObjectToType(Json.createValue(1), "Float"));
+ assertEquals(1.0, TypeConverter.convertObjectToType(Json.createValue(1), "double"));
+ assertEquals(1.0, TypeConverter.convertObjectToType(Json.createValue(1), "Double"));
+ assertEquals((byte) 1, TypeConverter.convertObjectToType(Json.createValue(1), "byte"));
+ assertEquals((byte) 1, TypeConverter.convertObjectToType(Json.createValue(1), "Byte"));
+ assertEquals((short) 1, TypeConverter.convertObjectToType(Json.createValue(1), "short"));
+ assertEquals((short) 1, TypeConverter.convertObjectToType(Json.createValue(1), "Short"));
+ assertEquals((char)1, TypeConverter.convertObjectToType(Json.createValue(1), "char"));
+ assertEquals(Character.valueOf((char)1), TypeConverter.convertObjectToType(Json.createValue(1), "Character"));
+ assertEquals(true, TypeConverter.convertObjectToType(Json.createValue(1), "boolean"));
+ assertEquals(true, TypeConverter.convertObjectToType(Json.createValue(1), "Boolean"));
+
+ assertArrayEquals(new String[] { "1" },
+ (String[]) TypeConverter.convertObjectToType(Json.createValue(1), "String[]"));
+ assertArrayEquals(new int[] { 1 }, (int[]) TypeConverter.convertObjectToType(Json.createValue(1), "int[]"));
+ assertArrayEquals(new Integer[] { 1 },
+ (Integer[]) TypeConverter.convertObjectToType(Json.createValue(1), "Integer[]"));
+ assertArrayEquals(new long[] { 1 },
+ (long[]) TypeConverter.convertObjectToType(Json.createValue(1), "long[]"));
+ assertArrayEquals(new Long[] { 1L },
+ (Long[]) TypeConverter.convertObjectToType(Json.createValue(1), "Long[]"));
+ assertArrayEquals(new float[] { 1f },
+ (float[]) TypeConverter.convertObjectToType(Json.createValue(1), "float[]"), 0.1f);
+ assertArrayEquals(new Float[] { 1f },
+ (Float[]) TypeConverter.convertObjectToType(Json.createValue(1), "Float[]"));
+ assertArrayEquals(new double[] { 1 },
+ (double[]) TypeConverter.convertObjectToType(Json.createValue(1), "double[]"), 0.1);
+ assertArrayEquals(new Double[] { 1.0 },
+ (Double[]) TypeConverter.convertObjectToType(Json.createValue(1), "Double[]"));
+ assertArrayEquals(new byte[] { 1 },
+ (byte[]) TypeConverter.convertObjectToType(Json.createValue(1), "byte[]"));
+ assertArrayEquals(new Byte[] { 1 },
+ (Byte[]) TypeConverter.convertObjectToType(Json.createValue(1), "Byte[]"));
+ assertArrayEquals(new short[] { 1 },
+ (short[]) TypeConverter.convertObjectToType(Json.createValue(1), "short[]"));
+ assertArrayEquals(new Short[] { 1 },
+ (Short[]) TypeConverter.convertObjectToType(Json.createValue(1), "Short[]"));
+ assertArrayEquals(new char[] { (char)1 },
+ (char[]) TypeConverter.convertObjectToType(Json.createValue(1), "char[]"));
+ assertArrayEquals(new Character[] { Character.valueOf((char)1) },
+ (Character[]) TypeConverter.convertObjectToType(Json.createValue(1), "Character[]"));
+ assertArrayEquals(new boolean[] { true },
+ (boolean[]) TypeConverter.convertObjectToType(Json.createValue(1), "boolean[]"));
+ assertArrayEquals(new Boolean[] { true },
+ (Boolean[]) TypeConverter.convertObjectToType(Json.createValue(1), "Boolean[]"));
+
+ assertArrayEquals(Collections.singletonList(1L).toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection")).toArray());
+ assertArrayEquals(Collections.singletonList("1").toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<String>"))
+ .toArray());
+ assertArrayEquals(new Integer[] { 1 },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Integer>"))
+ .toArray());
+ assertArrayEquals(new Long[] { 1L },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Long>"))
+ .toArray());
+ assertArrayEquals(new Float[] { 1f },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Float>"))
+ .toArray());
+ assertArrayEquals(new Double[] { 1.0 },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Double>"))
+ .toArray());
+ assertArrayEquals(new Byte[] { 1 },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Byte>"))
+ .toArray());
+ assertArrayEquals(new Short[] { 1 },
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Short>"))
+ .toArray());
+ assertArrayEquals(Arrays.asList(Character.valueOf((char)1)).toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Character>"))
+ .toArray());
+ assertArrayEquals(Collections.singletonList(Boolean.TRUE).toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Boolean>"))
+ .toArray());
+ }
+
+ @Test
+ public void testConvertBooleanToObjectWithType() {
+ assertEquals("true", TypeConverter.convertObjectToType(JsonValue.TRUE, "String"));
+ assertEquals(1, TypeConverter.convertObjectToType(JsonValue.TRUE, "int"));
+ assertEquals(1, TypeConverter.convertObjectToType(JsonValue.TRUE, "Integer"));
+ // uncomment below tests due to bug in Apache Felix Converter - FELIX-6242
+// assertEquals(1L, TypeConverter.convertObjectToType(JsonValue.TRUE, "long"));
+// assertEquals(1L, TypeConverter.convertObjectToType(JsonValue.TRUE, "Long"));
+// assertEquals(1.0f, TypeConverter.convertObjectToType(JsonValue.TRUE, "float"));
+// assertEquals(1.0f, TypeConverter.convertObjectToType(JsonValue.TRUE, "Float"));
+// assertEquals(1.0, TypeConverter.convertObjectToType(JsonValue.TRUE, "double"));
+// assertEquals(1.0, TypeConverter.convertObjectToType(JsonValue.TRUE, "Double"));
+// assertEquals((byte) 1, TypeConverter.convertObjectToType(JsonValue.TRUE, "byte"));
+// assertEquals((byte) 1, TypeConverter.convertObjectToType(JsonValue.TRUE, "Byte"));
+// assertEquals((short) 1, TypeConverter.convertObjectToType(JsonValue.TRUE, "short"));
+// assertEquals((short) 1, TypeConverter.convertObjectToType(JsonValue.TRUE, "Short"));
+ assertEquals((char)1, TypeConverter.convertObjectToType(JsonValue.TRUE, "char"));
+ assertEquals(Character.valueOf((char)1), TypeConverter.convertObjectToType(JsonValue.TRUE, "Character"));
+ assertEquals(true, TypeConverter.convertObjectToType(JsonValue.TRUE, "boolean"));
+ assertEquals(true, TypeConverter.convertObjectToType(JsonValue.TRUE, "Boolean"));
+
+ assertArrayEquals(new String[] { "true" },
+ (String[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "String[]"));
+ assertArrayEquals(new int[] { 1 }, (int[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "int[]"));
+ assertArrayEquals(new Integer[] { 1 },
+ (Integer[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Integer[]"));
+ assertArrayEquals(new long[] { 1 },
+ (long[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "long[]"));
+// assertArrayEquals(new Long[] { 1L },
+// (Long[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Long[]"));
+// assertArrayEquals(new float[] { 1f },
+// (float[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "float[]"), 0.1f);
+// assertArrayEquals(new Float[] { 1f },
+// (Float[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Float[]"));
+// assertArrayEquals(new double[] { 1 },
+// (double[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "double[]"), 0.1);
+// assertArrayEquals(new Double[] { 1.0 },
+// (Double[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Double[]"));
+// assertArrayEquals(new byte[] { 1 },
+// (byte[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "byte[]"));
+// assertArrayEquals(new Byte[] { 1 },
+// (Byte[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Byte[]"));
+// assertArrayEquals(new short[] { 1 },
+// (short[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "short[]"));
+// assertArrayEquals(new Short[] { 1 },
+// (Short[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Short[]"));
+ assertArrayEquals(new char[] { (char)1 },
+ (char[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "char[]"));
+ assertArrayEquals(new Character[] { Character.valueOf((char)1) },
+ (Character[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Character[]"));
+ assertArrayEquals(new boolean[] { true },
+ (boolean[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "boolean[]"));
+ assertArrayEquals(new Boolean[] { true },
+ (Boolean[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Boolean[]"));
+
+ assertArrayEquals(Collections.singletonList(Boolean.TRUE).toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection")).toArray());
+ assertArrayEquals(Collections.singletonList("true").toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<String>"))
+ .toArray());
+ assertArrayEquals(new Integer[] { 1 },
+ ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Integer>"))
+ .toArray());
+// assertArrayEquals(new Long[] { 1L },
+// ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Long>"))
+// .toArray());
+// assertArrayEquals(new Float[] { 1f },
+// ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Float>"))
+// .toArray());
+// assertArrayEquals(new Double[] { 1.0 },
+// ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Double>"))
+// .toArray());
+// assertArrayEquals(new Byte[] { 1 },
+// ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Byte>"))
+// .toArray());
+// assertArrayEquals(new Short[] { 1 },
+// ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Short>"))
+// .toArray());
+ assertArrayEquals(Arrays.asList(Character.valueOf((char)1)).toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Character>"))
+ .toArray());
+ assertArrayEquals(Collections.singletonList(Boolean.TRUE).toArray(),
+ ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Boolean>"))
+ .toArray());
+ }
+
+ private void assertEntry(final String typeInfo, final JsonValue jsonValue,
+ final Map.Entry<String, JsonValue> entry) {
+ assertEquals(typeInfo, entry.getKey());
+ assertNotNull(entry.getValue());
+ assertEquals(jsonValue, entry.getValue());
+ }
+
+ @Test public void testConvertScalarsToJson() throws Exception {
+ // null
+ assertEntry(TypeConverter.NO_TYPE_INFO, JsonValue.NULL,
+ TypeConverter.convertObjectToTypedJsonValue(null));
+
+ // String
+ assertEntry(TypeConverter.NO_TYPE_INFO, Json.createValue("hello"),
+ TypeConverter.convertObjectToTypedJsonValue("hello"));
+
+ // boolean and Boolean
+ assertEntry(TypeConverter.NO_TYPE_INFO,
+ JsonValue.TRUE, TypeConverter.convertObjectToTypedJsonValue(true));
+ assertEntry(TypeConverter.NO_TYPE_INFO, JsonValue.FALSE, TypeConverter.convertObjectToTypedJsonValue(false));
+ assertEntry(TypeConverter.NO_TYPE_INFO, JsonValue.TRUE,
+ TypeConverter.convertObjectToTypedJsonValue(Boolean.TRUE));
+ assertEntry(TypeConverter.NO_TYPE_INFO, JsonValue.FALSE,
+ TypeConverter.convertObjectToTypedJsonValue(Boolean.FALSE));
+
+ // double and Double
+ assertEntry(TypeConverter.NO_TYPE_INFO,
+ Json.createValue(1.0), TypeConverter.convertObjectToTypedJsonValue(1.0));
+ assertEntry(TypeConverter.NO_TYPE_INFO, Json.createValue(3.0),
+ TypeConverter.convertObjectToTypedJsonValue(Double.valueOf(3.0)));
+ // long and Long
+ assertEntry(TypeConverter.NO_TYPE_INFO,
+ Json.createValue((long) 5), TypeConverter.convertObjectToTypedJsonValue((long) 5));
+ assertEntry(TypeConverter.NO_TYPE_INFO, Json.createValue((long) 5),
+ TypeConverter.convertObjectToTypedJsonValue(Long.valueOf(5)));
+
+ // int and Integer
+ assertEntry("Integer", Json.createValue(5), TypeConverter.convertObjectToTypedJsonValue(5));
+ assertEntry("Integer", Json.createValue(5), TypeConverter.convertObjectToTypedJsonValue(Integer.valueOf(5)));
+
+ // float and Float
+ assertEntry("Float", Json.createValue(1.0),
+ TypeConverter.convertObjectToTypedJsonValue((float) 1.0));
+ assertEntry("Float", Json.createValue(3.0), TypeConverter.convertObjectToTypedJsonValue(Float.valueOf(3.0f)));
+
+ // byte and Byte
+ assertEntry("Byte", Json.createValue(5), TypeConverter.convertObjectToTypedJsonValue((byte) 5));
+ assertEntry("Byte", Json.createValue(5), TypeConverter.convertObjectToTypedJsonValue(Byte.valueOf((byte) 5)));
+
+ // short and Short
+ assertEntry("Short", Json.createValue(5),
+ TypeConverter.convertObjectToTypedJsonValue((short) 5));
+ assertEntry("Short", Json.createValue(5), TypeConverter.convertObjectToTypedJsonValue(Short.valueOf((short) 5)));
+
+ // char and Character
+ assertEntry("Character", Json.createValue("a"),
+ TypeConverter.convertObjectToTypedJsonValue('a'));
+ assertEntry("Character", Json.createValue("a"),
+ TypeConverter.convertObjectToTypedJsonValue(Character.valueOf('a')));
+ }
+
+ @Test
+ public void testConvertScalarArraysToJson() throws Exception {
+ // String[]
+ assertEntry(TypeConverter.NO_TYPE_INFO, Json.createArrayBuilder().add("hello").add("you").build(),
+ TypeConverter.convertObjectToTypedJsonValue(new String[] { "hello", "you" }));
+
+ // boolean[] and Boolean[]
+ assertEntry("boolean[]", Json.createArrayBuilder().add(true).add(false).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new boolean[] { true, false }));
+ assertEntry(TypeConverter.NO_TYPE_INFO, Json.createArrayBuilder().add(true).add(false).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new Boolean[] { true, false }));
+
+ // double[] and Double[]
+ assertEntry("double[]", Json.createArrayBuilder().add(2.0).add(1.0).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new double[] { 2.0, 1.0 }));
+ assertEntry(TypeConverter.NO_TYPE_INFO, Json.createArrayBuilder().add(2.0).add(1.0).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new Double[] { 2.0, 1.0 }));
+
+ // long[] and Long[]
+ assertEntry("long[]", Json.createArrayBuilder().add(2).add(1).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new long[] { 2, 1 }));
+ assertEntry(TypeConverter.NO_TYPE_INFO, Json.createArrayBuilder().add(2).add(1).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new Long[] { (long) 2, (long) 1 }));
+
+ // int[] and Integer[]
+ assertEntry("int[]", Json.createArrayBuilder().add(2).add(1).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new int[] { 2, 1 }));
+ assertEntry("Integer[]", Json.createArrayBuilder().add(2).add(1).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new Integer[] { 2, 1 }));
+
+ // float[] and Float[]
+ assertEntry("float[]", Json.createArrayBuilder().add(2.0).add(1.0).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new float[] { (float) 2.0, (float) 1.0 }));
+ assertEntry("Float[]", Json.createArrayBuilder().add(2.0).add(1.0).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new Float[] { (float) 2.0, (float) 1.0 }));
+
+ // byte[] and Byte[]
+ assertEntry("byte[]", Json.createArrayBuilder().add(2).add(1).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new byte[] { (byte) 2, (byte) 1 }));
+ assertEntry("Byte[]", Json.createArrayBuilder().add(2).add(1).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new Byte[] { (byte) 2, (byte) 1 }));
+
+ // short[] and Short[]
+ assertEntry("short[]", Json.createArrayBuilder().add(2).add(1).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new short[] { (short) 2, (short) 1 }));
+ assertEntry("Short[]", Json.createArrayBuilder().add(2).add(1).build(),
+ TypeConverter.convertObjectToTypedJsonValue(new Short[] { (short) 2, (short) 1 }));
+
+ // char[] and Character[]
+ assertEntry("char[]", Json.createArrayBuilder().add("a").add("b").build(),
+ TypeConverter.convertObjectToTypedJsonValue(new char[] { 'a', 'b' }));
+ assertEntry("Character[]", Json.createArrayBuilder().add("a").add("b").build(),
+ TypeConverter.convertObjectToTypedJsonValue(new Character[] { 'a', 'b' }));
+ }
+
+}
diff --git a/cm.json/src/test/resources/configs/bundle.json b/cm.json/src/test/resources/configs/bundle.json
new file mode 100644
index 0000000..3413edd
--- /dev/null
+++ b/cm.json/src/test/resources/configs/bundle.json
@@ -0,0 +1,8 @@
+{
+ "config.a" : {
+ "text" : "Hello World"
+ },
+ "config.b" : {
+ "port:Integer" : 8080
+ }
+}
\ No newline at end of file
diff --git a/cm.json/src/test/resources/configs/resource.json b/cm.json/src/test/resources/configs/resource.json
new file mode 100644
index 0000000..c2aebd1
--- /dev/null
+++ b/cm.json/src/test/resources/configs/resource.json
@@ -0,0 +1,10 @@
+{
+ ":configurator:version" : "1.0.0",
+ ":configurator:symbolic-name" : "feature",
+ "config.a" : {
+ "text" : "Hello World"
+ },
+ "config.b" : {
+ "port:Integer" : 8080
+ }
+}
\ No newline at end of file
diff --git a/cm.json/src/test/resources/configs/single.json b/cm.json/src/test/resources/configs/single.json
new file mode 100644
index 0000000..094cf5a
--- /dev/null
+++ b/cm.json/src/test/resources/configs/single.json
@@ -0,0 +1,3 @@
+{
+ "text" : "Hello World"
+}
\ No newline at end of file