refactor API

add (currently failing) tests
diff --git a/pom.xml b/pom.xml
index 0af896f..4c0453e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,29 +1,30 @@
 <?xml version="1.0" encoding="ISO-8859-1"?>
-    <!--
-        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
+<!--
+    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">
+    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.sling</groupId>
         <artifactId>sling</artifactId>
         <version>34</version>
-        <relativePath />
+        <relativePath/>
     </parent>
 
     <artifactId>org.apache.sling.feature.io</artifactId>
     <version>1.1.1-SNAPSHOT</version>
     <packaging>bundle</packaging>
-    
+
     <name>Apache Sling Feature IO Module</name>
     <description>
         IO functionality for the Feature Model
@@ -37,8 +38,8 @@
         <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-io.git</connection>
         <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-io.git</developerConnection>
         <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-feature-io.git</url>
-      <tag>HEAD</tag>
-  </scm>
+        <tag>HEAD</tag>
+    </scm>
 
     <build>
         <plugins>
@@ -184,5 +185,11 @@
             <version>1.0.0</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.9</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/feature/io/CloseShieldWriter.java b/src/main/java/org/apache/sling/feature/io/CloseShieldWriter.java
new file mode 100644
index 0000000..368fd28
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/io/CloseShieldWriter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.sling.feature.io;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Proxy writer that prevents the underlying writer from being closed.
+ * <p>
+ * This class is typically used in cases where a writer needs to be passed to a component that wants to explicitly close
+ * the writer even if other components would still use the writer for output.
+ * </p>
+ */
+public class CloseShieldWriter extends FilterWriter {
+
+    public CloseShieldWriter(Writer out) {
+        super(out);
+    }
+
+    @Override
+    public void close() throws IOException {
+        // NOOP
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/io/json/ConfigurationJSONWriter.java b/src/main/java/org/apache/sling/feature/io/json/ConfigurationJSONWriter.java
index fa309ae..ae637eb 100644
--- a/src/main/java/org/apache/sling/feature/io/json/ConfigurationJSONWriter.java
+++ b/src/main/java/org/apache/sling/feature/io/json/ConfigurationJSONWriter.java
@@ -54,11 +54,21 @@
     }
 
     /**
-     * Write the OSGi configuration to a JSON structure as defined in <a href="https://osgi.org/specification/osgi.cmpn/7.0.0/service.configurator.html#d0e131765">OSGi Configurator Specification 1.0</a> 
-     * @param generator The json generator
+     * Write the OSGi configuration to a JSON structure as defined in <a href="https://osgi.org/specification/osgi.cmpn/7.0.0/service.configurator.html#d0e131765">OSGi Configurator Specification 1.0</a>.
+     * The writer is not closed. 
+     * @param writer Writer
      * @param props The configuration properties to write
      */
-    public static void writeConfiguration(final JsonGenerator generator, final Dictionary<String, Object> props) {
+    public static void writeConfiguration(final Writer writer, final Dictionary<String, Object> props) {
+        final ConfigurationJSONWriter w = new ConfigurationJSONWriter();
+        JsonGenerator generator = w.newGenerator(writer);
+        generator.writeStartObject();
+        writeConfiguration(generator, props);
+        generator.writeEnd();
+        generator.close();
+    }
+
+    protected static void writeConfiguration(final JsonGenerator generator, final Dictionary<String, Object> props) {
         final Enumeration<String> e = props.keys();
         while ( e.hasMoreElements() ) {
             final String name = e.nextElement();
@@ -70,7 +80,7 @@
         }
     }
 
-    public static void writeConfigurationProperty(JsonGenerator generator, String name, Object val) {
+    protected static void writeConfigurationProperty(JsonGenerator generator, String name, Object val) {
         String typePostFix = null;
         final Object typeCheck;
         if ( val.getClass().isArray() ) {
diff --git a/src/main/java/org/apache/sling/feature/io/json/FeatureJSONWriter.java b/src/main/java/org/apache/sling/feature/io/json/FeatureJSONWriter.java
index 2c7af7c..af36576 100644
--- a/src/main/java/org/apache/sling/feature/io/json/FeatureJSONWriter.java
+++ b/src/main/java/org/apache/sling/feature/io/json/FeatureJSONWriter.java
@@ -47,6 +47,13 @@
     	// protected constructor for subclassing
     }
 
+    /**
+     * Writes the feature to the writer.
+     * The writer is not closed.
+     * @param writer Writer
+     * @param feature Feature
+     * @throws IOException If writing fails
+     */
     protected void writeFeature(final Writer writer, final Feature feature)
     throws IOException {
         JsonGenerator generator = newGenerator(writer);
diff --git a/src/main/java/org/apache/sling/feature/io/json/JSONWriterBase.java b/src/main/java/org/apache/sling/feature/io/json/JSONWriterBase.java
index cd8deb8..d88f288 100644
--- a/src/main/java/org/apache/sling/feature/io/json/JSONWriterBase.java
+++ b/src/main/java/org/apache/sling/feature/io/json/JSONWriterBase.java
@@ -35,6 +35,7 @@
 import org.apache.sling.feature.ExtensionType;
 import org.apache.sling.feature.MatchingRequirement;
 import org.apache.sling.feature.Prototype;
+import org.apache.sling.feature.io.CloseShieldWriter;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 
@@ -46,7 +47,9 @@
     private final JsonGeneratorFactory generatorFactory = Json.createGeneratorFactory(Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
 
     protected final JsonGenerator newGenerator(final Writer writer) {
-        return generatorFactory.createGenerator(writer);
+        // prevent closing of the underlying writer
+        Writer closeShieldWriter = new CloseShieldWriter(writer);
+        return generatorFactory.createGenerator(closeShieldWriter);
     }
 
     protected void writeBundles(final JsonGenerator generator,
diff --git a/src/test/java/org/apache/sling/feature/io/json/ConfigurationJSONWriterTest.java b/src/test/java/org/apache/sling/feature/io/json/ConfigurationJSONWriterTest.java
new file mode 100644
index 0000000..d40cfa8
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/io/json/ConfigurationJSONWriterTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.sling.feature.io.json;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.configurator.impl.json.JSONUtil;
+import org.apache.felix.configurator.impl.json.TypeConverter;
+import org.apache.felix.configurator.impl.model.ConfigurationFile;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ConfigurationJSONWriterTest {
+
+    @Test
+    public void testConfigurationWriteReadRoundtrip() throws IOException {
+        Dictionary<String, Object> props = new Hashtable<>();
+        props.put("Integer-simple", 1);
+        props.put("Integer-array", new Integer[]{1,2});
+        props.put("Integer-list", Arrays.asList(1, 2));
+        props.put("int-array", new int[]{1,2});
+        props.put("Long-simple", 1);
+        props.put("Long-array", new Long[]{1L,2L});
+        props.put("Long-list", Arrays.asList(1l, 2l));
+        props.put("long-array", new long[]{1,2});
+        props.put("Boolean-simple", Boolean.TRUE);
+        props.put("Boolean-array", new Boolean[] {Boolean.TRUE, Boolean.FALSE});
+        props.put("Boolean-list", Arrays.asList(Boolean.TRUE, Boolean.FALSE));
+        props.put("bool-array", new boolean[] {true, false});
+        props.put("Float-simple", 1.0d);
+        props.put("Float-array", new Float[]{1.0f, 2.0f});
+        props.put("Float-list", Arrays.asList(1.0f, 2.0f));
+        props.put("float-array", new float[]{1.0f,2.0f});
+        props.put("Double-simple", 1.0d);
+        props.put("Double-array", new Double[]{1.0d,2.0d});
+        props.put("Double-list", Arrays.asList(1.0d, 2.0d));
+        props.put("double-array", new double[]{1.0d,2.0d});
+        props.put("Byte-simple", new Byte((byte)1));
+        props.put("Byte-array", new Byte[]{1,2});
+        props.put("Byte-list", Arrays.asList((byte)1, (byte)2));
+        props.put("byte-array", new byte[]{1,2});
+        props.put("Short-simple", new Short((short) 1));
+        props.put("Short-array", new Short[]{1,2});
+        props.put("Short-list", Arrays.asList((short)1, (short)2));
+        props.put("Short-array", new short[]{1,2});
+        props.put("Character-simple", 1);
+        props.put("Character-array", new Character[]{'a','b'});
+        props.put("Character-list", Arrays.asList('a', 'b'));
+        props.put("char-array", new char[]{'a','b'});
+        props.put("String-simple", "test");
+        props.put("String-array", new String[]{"test1", "test2"});
+        props.put("String-list", Arrays.asList("test1", "test2"));
+        StringWriter writer = new StringWriter();
+        ConfigurationJSONWriter.writeConfiguration(writer, props);
+        writer.close();
+        assertConfigurationJson(writer.toString(), props);
+    }
+
+    protected void assertConfigurationJson(String json, Dictionary<String, Object> expectedProps) throws MalformedURLException {
+        final JSONUtil.Report report = new JSONUtil.Report();
+        StringBuilder sb = new StringBuilder("{ \"");
+        sb.append("myid");
+        sb.append("\" : ");
+        sb.append(json);
+        sb.append("}");
+        final ConfigurationFile configurationFile = JSONUtil.readJSON(new TypeConverter(null),"name", new URL("http://127.0.0.1/configtest"),
+                0, sb.toString(), report);
+        if ( !report.errors.isEmpty() || !report.warnings.isEmpty() ) {
+            Assert.fail("JSON is not the right format: \nErrors: " + StringUtils.join(report.errors) + "\nWarnings: " + StringUtils.join(report.warnings));
+        }
+        Assert.assertThat(configurationFile.getConfigurations().get(0).getProperties(), new DictionaryMatcher(expectedProps));
+    }
+    
+    public final static class DictionaryMatcher extends TypeSafeMatcher<Dictionary<String, Object>> {
+        private final Dictionary<String, Object> expectedDictionary;
+
+        public DictionaryMatcher(Dictionary<String, Object> dictionary) {
+            this.expectedDictionary = dictionary;
+        }
+        
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("Dictionary with items: ").appendValueList("", ",", "", toString(expectedDictionary));
+        }
+
+        
+        @Override
+        protected void describeMismatchSafely(Dictionary<String, Object> item, Description mismatchDescription) {
+            mismatchDescription.appendText("was Dictionary with items: ").appendValueList("", ",", "", toString(item));
+        }
+
+        static Iterable<String> toString(Dictionary<String, Object> dictionary) {
+            List<String> itemList = new LinkedList<String>();
+            Enumeration<String> e = dictionary.keys();
+            while (e.hasMoreElements()) {
+                final String key = e.nextElement();
+                Object value = dictionary.get(key);
+                // for arrays expand values
+                final String type = value.getClass().getSimpleName();
+                if (value.getClass().isArray()) {
+                    value = ArrayUtils.toString(value);
+                }
+                StringBuilder sb = new StringBuilder();
+                sb.append(key).append(":");
+                sb.append(value);
+                sb.append("(").append(type).append(")");
+                sb.append(", ");
+                itemList.add(sb.toString());
+            }
+            return itemList;
+        }
+
+        @Override
+        protected boolean matchesSafely(Dictionary<String, Object> item) {
+            return expectedDictionary.equals(item);
+        }
+        
+    }
+}