SLING-8419 move JSON configuration serialization code to dedicated
package with no dependencies to Sling Feature
diff --git a/src/main/java/org/apache/sling/feature/io/ConfiguratorUtil.java b/src/main/java/org/apache/sling/feature/io/ConfiguratorUtil.java
new file mode 100644
index 0000000..dfba0b5
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/io/ConfiguratorUtil.java
@@ -0,0 +1,175 @@
+/*
+ * 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.Writer;
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import javax.json.Json;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonGeneratorFactory;
+
+import org.apache.sling.feature.Configuration;
+
+/**
+ * Helper class to write JSON structures as defined in
+ * <a href="https://osgi.org/specification/osgi.cmpn/7.0.0/service.configurator.html#d0e131765">OSGi Configurator Specification 1.0</a>.
+ *
+ */
+public class ConfiguratorUtil {
+
+    private ConfiguratorUtil() {
+    }
+
+    protected static final JsonGenerator newGenerator(final Writer writer) {
+        JsonGeneratorFactory generatorFactory = Json.createGeneratorFactory(Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
+
+        // prevent closing of the underlying writer
+        Writer closeShieldWriter = new CloseShieldWriter(writer);
+        return generatorFactory.createGenerator(closeShieldWriter);
+    }
+
+    /** Write the OSGi configuration to a JSON structure.
+     * The writer is not closed.
+     * 
+     * @param writer Writer
+     * @param props The configuration properties to write */
+    public static void writeConfiguration(final Writer writer, final Dictionary<String, Object> props) {
+        try (JsonGenerator generator = newGenerator(writer)) {
+            generator.writeStartObject();
+            writeConfiguration(generator, props);
+            generator.writeEnd();
+        }
+    }
+
+    public 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();
+            if (Configuration.PROP_ARTIFACT_ID.equals(name)) {
+                continue;
+            }
+            final Object val = props.get(name);
+            writeConfigurationProperty(generator, name, val);
+        }
+    }
+
+    private static void writeConfigurationProperty(JsonGenerator generator, String name, Object val) {
+        String dataType = getDataType(val);
+        writeConfigurationProperty(generator, name, dataType, val);
+    }
+
+    private static void writeConfigurationProperty(JsonGenerator generator, String name, String dataType, Object val) {
+        String nameWithDataPostFix = name;
+        if (dataType != null) {
+            nameWithDataPostFix += ":" + dataType;
+        }
+        if (val.getClass().isArray()) {
+            generator.writeStartArray(nameWithDataPostFix);
+            for (int i = 0; i < Array.getLength(val); i++) {
+                writeArrayItem(generator, Array.get(val, i));
+            }
+            generator.writeEnd();
+        } else if (val instanceof Collection) {
+            generator.writeStartArray(nameWithDataPostFix);
+            for (Object item : Collection.class.cast(val)) {
+                writeArrayItem(generator, item);
+            }
+            generator.writeEnd();
+        } else {
+            writeNameValuePair(generator, nameWithDataPostFix, val);
+        }
+    }
+
+    private static void writeNameValuePair(JsonGenerator generator, String name, Object item) {
+        if (item instanceof Boolean) {
+            generator.write(name, (Boolean) item);
+        } else if (item instanceof Long || item instanceof Integer || item instanceof Byte || item instanceof Short) {
+            generator.write(name, ((Number)item).longValue());
+        } else if (item instanceof Double) {
+            generator.write(name, (Double) item);
+        } else if (item instanceof Float) {
+            generator.write(name, (Float) item);
+        } else {
+            generator.write(name, item.toString());
+        }
+    }
+
+    private static void writeArrayItem(JsonGenerator generator, Object item) {
+        if (item instanceof Boolean) {
+            generator.write((Boolean) item);
+        } else if (item instanceof Long || item instanceof Integer || item instanceof Byte || item instanceof Short) {
+            generator.write(((Number)item).longValue());
+        } else if (item instanceof Double) {
+            generator.write((Double) item);
+        } else if (item instanceof Float) {
+            generator.write((Float) item);
+        } else {
+            generator.write(item.toString());
+        }
+    }
+
+    private static String getDataType(Object object) {
+        if (object instanceof Collection) {
+            // check class of first item
+            Iterator<?> it = ((Collection<?>) object).iterator();
+            if (it.hasNext()) {
+                Class<?> itemClass = it.next().getClass();
+                return "Collection<" + getDataType(itemClass, false) + ">";
+            } else {
+                throw new IllegalStateException("Empty collections are invalid");
+            }
+        } else {
+            return getDataType(object.getClass(), true);
+        }
+    }
+
+    private static String getDataType(Class<?> clazz, boolean allowEmpty) {
+        if (clazz.isArray()) {
+            String dataType = getDataType(clazz.getComponentType(), false);
+            if (dataType != null) {
+                return dataType + "[]";
+            } else {
+                return null;
+            }
+        }
+        // default classes used by native JSON types
+        else if (clazz.isAssignableFrom(Boolean.class) || clazz.isAssignableFrom(boolean.class) || clazz.isAssignableFrom(Long.class) || clazz.isAssignableFrom(long.class) ||
+                clazz.isAssignableFrom(Double.class) || clazz.isAssignableFrom(double.class) || clazz.isAssignableFrom(String.class)) {
+            // no data type necessary except when being used in an array/collection
+            if (!allowEmpty) {
+                // for all other cases just use the simple name
+                return clazz.getSimpleName();
+            }
+
+        } else if (clazz.isAssignableFrom(Integer.class) || clazz.isAssignableFrom(int.class) || clazz.isAssignableFrom(Float.class) || clazz.isAssignableFrom(float.class)
+                || clazz.isAssignableFrom(Byte.class) || clazz.isAssignableFrom(byte.class) || clazz.isAssignableFrom(Short.class) || clazz.isAssignableFrom(short.class)
+                || clazz.isAssignableFrom(Character.class) || clazz.isAssignableFrom(char.class)) {
+            return clazz.getSimpleName();
+        }
+        if (!allowEmpty) {
+            throw new IllegalStateException("Class does not have a valid type " + clazz);
+        }
+        return null;
+
+    }
+}
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 2e57abe..fa3572d 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
@@ -50,130 +50,5 @@
         generator.close();
     }
 
-    /** 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 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();
-            if (Configuration.PROP_ARTIFACT_ID.equals(name)) {
-                continue;
-            }
-            final Object val = props.get(name);
-            writeConfigurationProperty(generator, name, val);
-        }
-    }
-
-    private static void writeConfigurationProperty(JsonGenerator generator, String name, Object val) {
-        String dataType = getDataType(val);
-        writeConfigurationProperty(generator, name, dataType, val);
-    }
-
-    private static void writeConfigurationProperty(JsonGenerator generator, String name, String dataType, Object val) {
-        String nameWithDataPostFix = name;
-        if (dataType != null) {
-            nameWithDataPostFix += ":" + dataType;
-        }
-        if (val.getClass().isArray()) {
-            generator.writeStartArray(nameWithDataPostFix);
-            for (int i = 0; i < Array.getLength(val); i++) {
-                writeArrayItem(generator, Array.get(val, i));
-            }
-            generator.writeEnd();
-        } else if (val instanceof Collection) {
-            generator.writeStartArray(nameWithDataPostFix);
-            for (Object item : Collection.class.cast(val)) {
-                writeArrayItem(generator, item);
-            }
-            generator.writeEnd();
-        } else {
-            writeNameValuePair(generator, nameWithDataPostFix, val);
-        }
-    }
-
-    private static void writeNameValuePair(JsonGenerator generator, String name, Object item) {
-        if (item instanceof Boolean) {
-            generator.write(name, (Boolean) item);
-        } else if (item instanceof Long || item instanceof Integer || item instanceof Byte || item instanceof Short) {
-            generator.write(name, ((Number)item).longValue());
-        } else if (item instanceof Double) {
-            generator.write(name, (Double) item);
-        } else if (item instanceof Float) {
-            generator.write(name, (Float) item);
-        } else {
-            generator.write(name, item.toString());
-        }
-    }
-
-    private static void writeArrayItem(JsonGenerator generator, Object item) {
-        if (item instanceof Boolean) {
-            generator.write((Boolean) item);
-        } else if (item instanceof Long || item instanceof Integer || item instanceof Byte || item instanceof Short) {
-            generator.write(((Number)item).longValue());
-        } else if (item instanceof Double) {
-            generator.write((Double) item);
-        } else if (item instanceof Float) {
-            generator.write((Float) item);
-        } else {
-            generator.write(item.toString());
-        }
-    }
-
-    private static String getDataType(Object object) {
-        if (object instanceof Collection) {
-            // check class of first item
-            Iterator<?> it = ((Collection<?>) object).iterator();
-            if (it.hasNext()) {
-                Class<?> itemClass = it.next().getClass();
-                return "Collection<" + getDataType(itemClass, false) + ">";
-            } else {
-                throw new IllegalStateException("Empty collections are invalid");
-            }
-        } else {
-            return getDataType(object.getClass(), true);
-        }
-    }
-
-    private static String getDataType(Class<?> clazz, boolean allowEmpty) {
-        if (clazz.isArray()) {
-            String dataType = getDataType(clazz.getComponentType(), false);
-            if (dataType != null) {
-                return dataType + "[]";
-            } else {
-                return null;
-            }
-        }
-        // default classes used by native JSON types
-        else if (clazz.isAssignableFrom(Boolean.class) || clazz.isAssignableFrom(boolean.class) || clazz.isAssignableFrom(Long.class) || clazz.isAssignableFrom(long.class) ||
-                clazz.isAssignableFrom(Double.class) || clazz.isAssignableFrom(double.class) || clazz.isAssignableFrom(String.class)) {
-            // no data type necessary except when being used in an array/collection
-            if (!allowEmpty) {
-                // for all other cases just use the simple name
-                return clazz.getSimpleName();
-            }
-
-        } else if (clazz.isAssignableFrom(Integer.class) || clazz.isAssignableFrom(int.class) || clazz.isAssignableFrom(Float.class) || clazz.isAssignableFrom(float.class)
-                || clazz.isAssignableFrom(Byte.class) || clazz.isAssignableFrom(byte.class) || clazz.isAssignableFrom(Short.class) || clazz.isAssignableFrom(short.class)
-                || clazz.isAssignableFrom(Character.class) || clazz.isAssignableFrom(char.class)) {
-            return clazz.getSimpleName();
-        }
-        if (!allowEmpty) {
-            throw new IllegalStateException("Class does not have a valid type " + clazz);
-        }
-        return null;
-
-    }
+    
 }
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 d88f288..b74a4db 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
@@ -36,6 +36,7 @@
 import org.apache.sling.feature.MatchingRequirement;
 import org.apache.sling.feature.Prototype;
 import org.apache.sling.feature.io.CloseShieldWriter;
+import org.apache.sling.feature.io.ConfiguratorUtil;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 
@@ -105,7 +106,7 @@
 
         for(final Configuration cfg : cfgs) {
             generator.writeStartObject(cfg.getPid());
-            ConfigurationJSONWriter.writeConfiguration(generator, cfg.getConfigurationProperties());
+            ConfiguratorUtil.writeConfiguration(generator, cfg.getConfigurationProperties());
             generator.writeEnd();
         }
 
diff --git a/src/test/java/org/apache/sling/feature/io/json/ConfigurationJSONWriterTest.java b/src/test/java/org/apache/sling/feature/io/ConfiguratorUtilTest.java
similarity index 96%
rename from src/test/java/org/apache/sling/feature/io/json/ConfigurationJSONWriterTest.java
rename to src/test/java/org/apache/sling/feature/io/ConfiguratorUtilTest.java
index 8dac7c1..46b6ba8 100644
--- a/src/test/java/org/apache/sling/feature/io/json/ConfigurationJSONWriterTest.java
+++ b/src/test/java/org/apache/sling/feature/io/ConfiguratorUtilTest.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sling.feature.io.json;
+package org.apache.sling.feature.io;
 
 import java.io.IOException;
 import java.io.StringWriter;
@@ -30,6 +30,7 @@
 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.apache.sling.feature.io.ConfiguratorUtil;
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeDiagnosingMatcher;
 import org.hamcrest.core.Every;
@@ -39,7 +40,7 @@
 import org.osgi.util.converter.Converters;
 import org.osgi.util.converter.TypeReference;
 
-public class ConfigurationJSONWriterTest {
+public class ConfiguratorUtilTest {
 
     @Test
     public void testConfigurationWriteReadRoundtrip() throws IOException {
@@ -80,7 +81,7 @@
         props.put("String-array", new String[]{"test1", "test2"});
         props.put("String-list", Arrays.asList("test1", "test2"));
         StringWriter writer = new StringWriter();
-        ConfigurationJSONWriter.writeConfiguration(writer, props);
+        ConfiguratorUtil.writeConfiguration(writer, props);
         writer.close();
         assertConfigurationJson(writer.toString(), props);
     }