You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by da...@apache.org on 2016/08/25 12:58:23 UTC

svn commit: r1757677 [1/3] - in /felix/trunk/converter: ./ converter/ converter/src/ converter/src/main/ converter/src/main/java/ converter/src/main/java/org/ converter/src/main/java/org/apache/ converter/src/main/java/org/apache/felix/ converter/src/m...

Author: davidb
Date: Thu Aug 25 12:58:22 2016
New Revision: 1757677

URL: http://svn.apache.org/viewvc?rev=1757677&view=rev
Log:
Felix Converter Service - move converter into a submodule

Added:
    felix/trunk/converter/converter/
    felix/trunk/converter/converter/pom.xml
    felix/trunk/converter/converter/src/
    felix/trunk/converter/converter/src/main/
    felix/trunk/converter/converter/src/main/java/
    felix/trunk/converter/converter/src/main/java/org/
    felix/trunk/converter/converter/src/main/java/org/apache/
    felix/trunk/converter/converter/src/main/java/org/apache/felix/
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Activator.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConverterImpl.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConverterService.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/InternalConverter.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/InternalConverting.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Util.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonCodecImpl.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonDecodingImpl.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonEncodingImpl.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonParser.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/yaml/
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/yaml/YamlCodecImpl.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/yaml/YamlDecodingImpl.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/yaml/YamlEncodingImpl.java
    felix/trunk/converter/converter/src/main/java/org/osgi/
    felix/trunk/converter/converter/src/main/java/org/osgi/service/
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/Adapter.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/Codec.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/ConversionException.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/ConvertFunction.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/Converter.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/Converting.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/Decoding.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/Encoding.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/Rule.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/SimpleConvertFunction.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/TypeReference.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/package-info.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/util/
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/util/ConverterFactory.java
    felix/trunk/converter/converter/src/main/java/org/osgi/service/converter/util/package-info.java
    felix/trunk/converter/converter/src/main/resources/
    felix/trunk/converter/converter/src/main/resources/META-INF/
    felix/trunk/converter/converter/src/main/resources/META-INF/services/
    felix/trunk/converter/converter/src/main/resources/META-INF/services/org.osgi.service.converter.Converter
    felix/trunk/converter/converter/src/test/
    felix/trunk/converter/converter/src/test/java/
    felix/trunk/converter/converter/src/test/java/org/
    felix/trunk/converter/converter/src/test/java/org/apache/
    felix/trunk/converter/converter/src/test/java/org/apache/felix/
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/AdapterTest.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterMapTest.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterServiceTest.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/MyBean.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/MyDTO.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/MyEmbeddedDTO.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/MySubDTO.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/json/
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/json/JsonCodecTest.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/json/JsonParserTest.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/json/JsonSerializationTest.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/yaml/
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/yaml/YamlSerializationTest.java
Removed:
    felix/trunk/converter/src/
Modified:
    felix/trunk/converter/pom.xml

Added: felix/trunk/converter/converter/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/pom.xml?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/pom.xml (added)
+++ felix/trunk/converter/converter/pom.xml Thu Aug 25 12:58:22 2016
@@ -0,0 +1,133 @@
+<!--
+    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>4</version>
+        <relativePath>../pom/pom.xml</relativePath>
+    </parent>
+
+    <name>Apache Felix Converter Service</name>
+    <artifactId>org.apache.felix.converter</artifactId>
+    <version>0.1-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/converter</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/converter</developerConnection>
+        <url>http://svn.apache.org/viewvc/felix/trunk/converter/</url>
+    </scm>
+
+    <properties>
+        <felix.java.version>8</felix.java.version>
+        <felix.java.signature.artifactId>java18</felix.java.signature.artifactId>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>2.5.3</version>
+                <executions>
+                    <execution>
+                        <id>bundle</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>bundle</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>baseline</id>
+                        <goals>
+                            <goal>baseline</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>org.apache.felix.converter.impl.Activator</Bundle-Activator>
+                        <Private-Package>org.apache.felix.converter.*,org.yaml.snakeyaml.*</Private-Package>
+                        <Export-Package>org.osgi.service.converter, org.osgi.service.converter.util</Export-Package>
+                        <Import-Package>*</Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <includes>
+                        <include>src/**</include>
+                    </includes>
+                    <excludes>
+                        <exclude>src/main/resources/META-INF/services/org.osgi.service.converter.Converter</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>osgi.core</artifactId>
+            <version>6.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+            <version>1.17</version>
+        </dependency>
+        
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.json</artifactId>
+            <version>2.0.16</version>
+            <scope>test</scope>            
+        </dependency>
+    </dependencies>
+</project>

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Activator.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Activator.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Activator.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Activator.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,50 @@
+/*
+ * 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.converter.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.converter.impl.json.JsonCodecImpl;
+import org.apache.felix.converter.impl.yaml.YamlCodecImpl;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.converter.Codec;
+import org.osgi.service.converter.Converter;
+
+public class Activator implements BundleActivator {
+    @Override
+    public void start(BundleContext context) throws Exception {
+        context.registerService(Converter.class, new ConverterService(), null);
+
+        Dictionary<String, Object> jsonProps = new Hashtable<>();
+        jsonProps.put("osgi.codec.mimetype", new String[] {
+                "application/json", "application/x-javascript", "text/javascript",
+                "text/x-javascript", "text/x-json" });
+        context.registerService(Codec.class, new JsonCodecImpl(), jsonProps);
+
+        Dictionary<String, Object> yamlProps = new Hashtable<>();
+        yamlProps.put("osgi.codec.mimetype", new String[] {
+                "text/yaml", "text/x-yaml", "application/yaml",
+                "application/x-yaml" });
+        context.registerService(Codec.class, new YamlCodecImpl(), yamlProps);
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+    }
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,229 @@
+/*
+ * 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.converter.impl;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.osgi.service.converter.Adapter;
+import org.osgi.service.converter.ConversionException;
+import org.osgi.service.converter.ConvertFunction;
+import org.osgi.service.converter.Converter;
+import org.osgi.service.converter.Converting;
+import org.osgi.service.converter.Rule;
+import org.osgi.service.converter.SimpleConvertFunction;
+import org.osgi.service.converter.TypeReference;
+
+public class AdapterImpl implements Adapter, InternalConverter {
+    private final InternalConverter delegate;
+    private final Map<TypePair, ConvertFunction<Object, Object>> classRules =
+            new ConcurrentHashMap<>();
+
+    AdapterImpl(InternalConverter converter) {
+        this.delegate = converter;
+    }
+
+    @Override
+    public InternalConverting convert(Object obj) {
+        InternalConverting converting = delegate.convert(obj);
+        converting.setConverter(this);
+        return new ConvertingWrapper(obj, converting);
+    }
+
+    @Override
+    public Adapter getAdapter() {
+        return new AdapterImpl(this);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <F, T> Adapter rule(Class<F> fromCls, Class<T> toCls,
+            SimpleConvertFunction<F, T> toFun, SimpleConvertFunction<T, F> fromFun) {
+        if (fromCls.equals(toCls))
+            throw new IllegalArgumentException();
+
+        classRules.put(new TypePair(fromCls, toCls), (ConvertFunction<Object, Object>) toFun);
+        classRules.put(new TypePair(toCls, fromCls), (ConvertFunction<Object, Object>) fromFun);
+        return this;
+    }
+
+    @Override
+    public <F, T> Adapter rule(TypeReference<F> fromRef, TypeReference<T> toRef,
+            SimpleConvertFunction<F, T> toFun, SimpleConvertFunction<T, F> fromFun) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <F, T> Adapter rule(Type fromType, Type toType,
+            SimpleConvertFunction<F, T> toFun, SimpleConvertFunction<T, F> fromFun) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <F, T> Adapter rule(Rule<F, T> rule) {
+        ConvertFunction<F, T> toFun = rule.getToFunction();
+        if (toFun != null)
+            classRules.put(new TypePair(rule.getFromClass(), rule.getToClass()),
+                (ConvertFunction<Object, Object>) toFun);
+
+
+        ConvertFunction<T, F> fromFun = rule.getFromFunction();
+        if (fromFun != null)
+            classRules.put(new TypePair(rule.getToClass(), rule.getFromClass()),
+                (ConvertFunction<Object, Object>) fromFun);
+        return this;
+    }
+
+    private class ConvertingWrapper implements InternalConverting {
+        private final InternalConverting del;
+        private final Object object;
+        private volatile Object defaultValue;
+        private volatile boolean hasDefault;
+
+        ConvertingWrapper(Object obj, InternalConverting c) {
+            object = obj;
+            del = c;
+        }
+
+        @Override
+        public Converting defaultValue(Object defVal) {
+            del.defaultValue(defVal);
+            defaultValue = defVal;
+            hasDefault = true;
+            return this;
+        }
+
+        @Override
+        public void setConverter(Converter c) {
+            del.setConverter(c);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> T to(Class<T> cls)  {
+            Type type = cls;
+            return (T) to(type);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> T to(TypeReference<T> ref)  {
+            return (T) to(ref.getType());
+        }
+
+        @Override
+        public Object to(Type type) {
+            if (object != null) {
+                Set<Type> fromTypes = assignableTypes(object.getClass());
+                Set<Type> toTypes = assignableTypes(type);
+
+                List<ConvertFunction<Object, Object>> converters = new ArrayList<>();
+                for (Type fromType : fromTypes) {
+                    for (Type toType : toTypes) {
+                        // TODO what exactly do we use as order here?
+                        converters.add(classRules.get(new TypePair(fromType, Util.primitiveToBoxed(toType))));
+                    }
+                }
+                for (Type fromType : fromTypes) {
+                    converters.add(classRules.get(new TypePair(fromType, Object.class)));
+                }
+                for (Type toType : toTypes) {
+                    converters.add(classRules.get(new TypePair(Object.class, Util.primitiveToBoxed(toType))));
+                }
+
+                for (Iterator<ConvertFunction<Object, Object>> it = converters.iterator(); it.hasNext(); ) {
+                    ConvertFunction<Object, Object> func = it.next();
+                    it.remove();
+                    if (func == null)
+                        continue;
+
+                    try {
+                        Object res = func.convert(object, type);
+                        if (res != ConvertFunction.CANNOT_CONVERT)
+                            return res;
+                    } catch (Exception ex) {
+                        if (hasDefault)
+                            return defaultValue;
+                        else
+                            throw new ConversionException("Cannot convert " + object + " to " + type, ex);
+                    }
+                }
+            }
+
+            return del.to(type);
+        }
+
+        @Override
+        public String toString() {
+            return to(String.class);
+        }
+    }
+
+    private static Set<Type> assignableTypes(Type mostSpecialized) {
+        if (!(mostSpecialized instanceof Class))
+            return Collections.singleton(mostSpecialized);
+
+        Class<?> curClass = (Class<?>) mostSpecialized;
+        Set<Type> lookupTypes = new LinkedHashSet<>(); // Iteration order matters!
+        while((curClass != null) && (!(Object.class.equals(curClass)))) {
+            lookupTypes.add(curClass);
+            lookupTypes.addAll(Arrays.asList(curClass.getInterfaces()));
+            curClass = curClass.getSuperclass();
+        }
+        lookupTypes.add(Object.class); // Object is the superclass of any type
+        return lookupTypes;
+    }
+
+    static class TypePair {
+        private final Type from;
+        private final Type to;
+
+        TypePair(Type from, Type to) {
+            this.from = from;
+            this.to = to;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(from, to);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this)
+                return true;
+            if (!(obj instanceof TypePair))
+                return false;
+
+            TypePair o = (TypePair) obj;
+            return Objects.equals(from, o.from) &&
+                    Objects.equals(to, o.to);
+        }
+    }
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConverterImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConverterImpl.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConverterImpl.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConverterImpl.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,31 @@
+/*
+ * 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.converter.impl;
+
+import org.osgi.service.converter.Adapter;
+
+public class ConverterImpl implements InternalConverter {
+    @Override
+    public Adapter getAdapter() {
+        return new AdapterImpl(this);
+    }
+
+    @Override
+    public InternalConverting convert(Object obj) {
+        return new ConvertingImpl(this, obj);
+    }
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConverterService.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConverterService.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConverterService.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConverterService.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,69 @@
+/*
+ * 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.converter.impl;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.ZonedDateTime;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import org.osgi.service.converter.Adapter;
+import org.osgi.service.converter.Converter;
+import org.osgi.service.converter.Converting;
+
+public class ConverterService implements Converter {
+    private final Adapter adapter;
+
+    public ConverterService() {
+        Adapter a = new ConverterImpl().getAdapter();
+        a.rule(Byte.class, String.class, v -> v.toString(), Byte::parseByte); // TODO test
+        a.rule(Character.class, Boolean.class, v -> v.charValue() != 0,
+                v -> v.booleanValue() ? (char) 1 : (char) 0);
+        a.rule(Character.class, String.class, v -> v.toString(),
+                v -> v.length() > 0 ? v.charAt(0) : 0);
+        a.rule(Class.class, String.class, Class::toString,
+                v -> getClass().getClassLoader().loadClass(v));
+        a.rule(Double.class, String.class, v -> v.toString(), Double::parseDouble); // TODO test
+        a.rule(Float.class, String.class, v -> v.toString(), Float::parseFloat); // TODO test
+        a.rule(Integer.class, String.class, v -> v.toString(), Integer::parseInt);
+        a.rule(LocalDateTime.class, String.class, LocalDateTime::toString, LocalDateTime::parse);
+        a.rule(LocalDate.class, String.class, LocalDate::toString, LocalDate::parse);
+        a.rule(LocalTime.class, String.class, LocalTime::toString, LocalTime::parse);
+        a.rule(Long.class, String.class, v -> v.toString(), Long::parseLong); // TODO test
+        a.rule(OffsetDateTime.class, String.class, OffsetDateTime::toString, OffsetDateTime::parse);
+        a.rule(OffsetTime.class, String.class, OffsetTime::toString, OffsetTime::parse);
+        a.rule(Pattern.class, String.class, Pattern::toString, Pattern::compile);
+        a.rule(Short.class, String.class, v -> v.toString(), Short::parseShort); // TODO test
+        a.rule(UUID.class, String.class, UUID::toString, UUID::fromString);
+        a.rule(ZonedDateTime.class, String.class, ZonedDateTime::toString, ZonedDateTime::parse);
+        adapter = a;
+    }
+
+    @Override
+    public Converting convert(Object obj) {
+        return adapter.convert(obj);
+    }
+
+    @Override
+    public Adapter getAdapter() {
+        return adapter.getAdapter();
+    }
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,675 @@
+/*
+ * 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.converter.impl;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.osgi.dto.DTO;
+import org.osgi.service.converter.ConversionException;
+import org.osgi.service.converter.Converter;
+import org.osgi.service.converter.Converting;
+import org.osgi.service.converter.TypeReference;
+
+public class ConvertingImpl implements Converting, InternalConverting {
+    private static final Map<Class<?>, Class<?>> interfaceImplementations;
+    static {
+        Map<Class<?>, Class<?>> m = new HashMap<>();
+        m.put(Collection.class, ArrayList.class);
+        m.put(List.class, ArrayList.class);
+        m.put(Set.class, LinkedHashSet.class); // preserves insertion order
+        m.put(Map.class, LinkedHashMap.class); // preserves insertion order
+        interfaceImplementations = Collections.unmodifiableMap(m);
+    }
+
+    private volatile Converter converter;
+    private volatile Object object;
+    private volatile Object defaultValue;
+    private volatile boolean hasDefault;
+
+    ConvertingImpl(Converter c, Object obj) {
+        converter = c;
+        object = obj;
+    }
+
+    @Override
+    public Converting defaultValue(Object defVal) {
+        defaultValue = defVal;
+        hasDefault = true;
+
+        return this;
+    }
+
+    @Override
+    public void setConverter(Converter c) {
+        converter = c;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> T to(Class<T> cls)  {
+        Type type = cls;
+        return (T) to(type);
+    }
+
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> T to(TypeReference<T> ref)  {
+        return (T) to(ref.getType());
+    }
+
+    @Override
+    public Object to(Type type) {
+        Class<?> cls = null;
+        Type[] typeArguments = null;
+        if (type instanceof Class) {
+            cls = (Class<?>) type;
+        } else if (type instanceof ParameterizedType) {
+            ParameterizedType pt = (ParameterizedType) type;
+            Type rt = pt.getRawType();
+            typeArguments = pt.getActualTypeArguments();
+            if (rt instanceof Class)
+                cls = (Class<?>) rt;
+        }
+        if (cls == null)
+            return null;
+
+        if (object == null)
+            return handleNull(cls);
+
+        Class<?> targetCls = Util.primitiveToBoxed(cls);
+
+        if (!Map.class.isAssignableFrom(targetCls) &&
+                !Collection.class.isAssignableFrom(targetCls) &&
+                !targetCls.isArray()) {
+            // For maps and collections we always want copies returned
+            if (targetCls.isAssignableFrom(object.getClass()))
+                return object;
+        }
+
+        Object res = trySpecialCases(targetCls);
+        if (res != null)
+            return res;
+
+        if (targetCls.isArray()) {
+            return convertToArray(targetCls);
+        } else if (Collection.class.isAssignableFrom(targetCls)) {
+            return convertToCollection(targetCls, typeArguments);
+        } else if (DTO.class.isAssignableFrom(targetCls)) {
+            return convertToDTO(targetCls);
+        } else if (isMapType(targetCls)) {
+            return convertToMapType(targetCls, typeArguments);
+        }
+
+        // At this point we know that the target is a 'singular' type: not a map, collection or array
+        if (object instanceof Collection) {
+            return convertCollectionToSingleValue(cls);
+        } else if ((object = asBoxedArray(object)) instanceof Object[]) {
+            return convertArrayToSingleValue(cls);
+        }
+
+        Object res2 = tryStandardMethods(targetCls);
+        if (res2 != null) {
+            return res2;
+        } else {
+            if (defaultValue != null)
+                return converter.convert(defaultValue).to(targetCls);
+            else
+                return null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return to(String.class);
+    }
+
+    private Object convertArrayToSingleValue(Class<?> cls) {
+        Object[] arr = (Object[]) object;
+        if (arr.length == 0)
+            return null;
+        else
+            return converter.convert(arr[0]).to(cls);
+    }
+
+    private Object convertCollectionToSingleValue(Class<?> cls) {
+        Collection<?> coll = (Collection<?>) object;
+        if (coll.size() == 0)
+            return null;
+        else
+            return converter.convert(coll.iterator().next()).to(cls);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T convertToArray(Class<?> targetClass) {
+        Collection<?> collectionView = collectionView(object);
+        Iterator<?> itertor = collectionView.iterator();
+        try {
+            Object array = Array.newInstance(targetClass.getComponentType(), collectionView.size());
+            for (int i=0; i<collectionView.size() && itertor.hasNext(); i++) {
+                Object next = itertor.next();
+                Object converted = converter.convert(next).to(targetClass.getComponentType());
+                Array.set(array, i, converted);
+            }
+            return (T) array;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private <T> T convertToCollection(Class<?> targetCls, Type[] typeArguments) {
+        Collection<?> cv = collectionView(object);
+        Class<?> targetElementType = null;
+        if (typeArguments != null && typeArguments.length > 0 && typeArguments[0] instanceof Class) {
+            targetElementType = (Class<?>) typeArguments[0];
+        }
+
+        Class<?> ctrCls = interfaceImplementations.get(targetCls);
+        if (ctrCls != null)
+            targetCls = ctrCls;
+
+        Collection instance = (Collection) createMapOrCollection(targetCls, cv.size());
+        if (instance == null)
+            return null;
+
+        for (Object o : cv) {
+            if (targetElementType != null)
+                o = converter.convert(o).to(targetElementType);
+
+            instance.add(o);
+        }
+
+        return (T) instance;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private <T> T convertToDTO(Class<T> targetCls) {
+        Map m = mapView(object, converter);
+
+        try {
+            T dto = targetCls.newInstance();
+
+            for (Map.Entry entry : (Set<Map.Entry>) m.entrySet()) {
+                try {
+                    Field f = targetCls.getField(entry.getKey().toString());
+                    Object val = entry.getValue();
+                    f.set(dto, converter.convert(val).to(f.getType()));
+                } catch (NoSuchFieldException e) {
+                }
+            }
+
+            return dto;
+        } catch (Exception e) {
+            throw new ConversionException("Cannot create DTO " + targetCls, e);
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private Map convertToMap(Class<?> targetCls, Type[] typeArguments) {
+        Map m = mapView(object, converter);
+        if (m == null)
+            return null;
+        Class<?> targetKeyType = null, targetValueType = null;
+        if (typeArguments != null && typeArguments.length > 1 &&
+                typeArguments[0] instanceof Class && typeArguments[1] instanceof Class) {
+            targetKeyType = (Class<?>) typeArguments[0];
+            targetValueType = (Class<?>) typeArguments[1];
+        }
+
+        Class<?> ctrCls = interfaceImplementations.get(targetCls);
+        if (ctrCls == null)
+            ctrCls = targetCls;
+
+        Map instance = (Map) createMapOrCollection(ctrCls, m.size());
+        if (instance == null)
+            return null;
+
+        for (Map.Entry entry : (Set<Entry>) m.entrySet()) {
+            Object key = entry.getKey();
+            if (targetKeyType != null)
+                key = converter.convert(key).to(targetKeyType);
+            Object value = entry.getValue();
+            if (targetValueType != null)
+                value = converter.convert(value).to(targetValueType);
+            instance.put(key, value);
+        }
+
+        return instance;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private Object convertToMapType(Class<?> targetCls, Type[] typeArguments) {
+        if (Map.class.isAssignableFrom(targetCls))
+            return convertToMap(targetCls, typeArguments);
+        else if (Dictionary.class.isAssignableFrom(targetCls))
+            return new Hashtable(convertToMap(Map.class, typeArguments));
+        else if (targetCls.isInterface())
+            return createProxy(targetCls);
+        return createJavaBean(targetCls);
+    }
+
+    private Object createJavaBean(Class<?> targetCls) {
+        @SuppressWarnings("rawtypes")
+        Map m = mapView(object, converter);
+        try {
+            Object res = targetCls.getConstructor().newInstance();
+            for (Method setter : getSetters(targetCls)) {
+                String setterName = setter.getName();
+                StringBuilder propName = new StringBuilder(Character.valueOf(Character.toLowerCase(setterName.charAt(3))).toString());
+                if (setterName.length() > 4)
+                    propName.append(setterName.substring(4));
+
+                Class<?> setterType = setter.getParameterTypes()[0];
+                setter.invoke(res, converter.convert(m.get(propName.toString())).to(setterType));
+            }
+            return res;
+        } catch (Exception e) {
+            throw new ConversionException("Cannot convert to class: " + targetCls.getName() +
+                    ". Not a JavaBean with a Zero-arg Constructor.", e);
+        }
+    }
+
+    @SuppressWarnings("rawtypes")
+    private Object createProxy(Class<?> targetCls) {
+        Map m = mapView(object, converter);
+        return Proxy.newProxyInstance(targetCls.getClassLoader(), new Class[] {targetCls},
+            new InvocationHandler() {
+                @Override
+                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                    String propName = getInterfacePropertyName(method);
+                    if (propName == null)
+                        return null;
+
+                    Class<?> targetType = method.getReturnType();
+
+                    Object val = m.get(propName);
+
+                    // If no value is available take the default if specified
+                    if (val == null) {
+                        if (targetCls.isAnnotation()) {
+                            val = method.getDefaultValue();
+                        }
+
+                        if (val == null && args != null && args.length == 1)
+                            val = args[0];
+                    }
+                    return converter.convert(val).to(targetType);
+                }
+            });
+    }
+
+    private Object handleNull(Class<?> cls) {
+        if (hasDefault)
+            return converter.convert(defaultValue).to(cls);
+
+        Class<?> boxed = Util.primitiveToBoxed(cls);
+        if (boxed.equals(cls)) {
+            // This is not a primitive, just return null
+            return null;
+        }
+        if (cls.equals(boolean.class)) {
+            return false;
+        } else if (cls.equals(long.class) ) {
+            return 0L;
+        } else if (cls.equals(double.class) ) {
+            return 0.0;
+        } else {
+            return 0;
+        }
+    }
+
+    private boolean isMapType(Class<?> targetCls) {
+        // All interface types that are not Collections are treated as maps
+        if (targetCls.isInterface())
+            return true;
+        else if (isWriteableJavaBean(targetCls))
+            return true;
+        else
+            return Dictionary.class.isAssignableFrom(targetCls);
+    }
+
+    private Object trySpecialCases(Class<?> targetCls) {
+        // TODO some of these can probably be implemented as an adapter
+
+        if (Boolean.class.equals(targetCls)) {
+            if (object instanceof Number) {
+                return ((Number) object).longValue() != 0;
+            } else if (object instanceof Collection && ((Collection<?>) object).size() == 0) {
+                // TODO What about arrays?
+                return Boolean.FALSE;
+            }
+        } else if (Character.class.equals(targetCls)) {
+            if (object instanceof Number) {
+                return Character.valueOf((char) ((Number) object).intValue());
+            }
+        } else if (Number.class.isAssignableFrom(targetCls)) {
+            if (object instanceof Boolean) {
+                return ((Boolean) object).booleanValue() ? 1 : 0;
+            }
+        } else if (Class.class.equals(targetCls)) {
+            if (object instanceof Collection && ((Collection<?>) object).size() == 0) {
+                return null;
+            }
+        } else if (Enum.class.isAssignableFrom(targetCls)) {
+            if (object instanceof Boolean) {
+                try {
+                    Method m = targetCls.getMethod("valueOf", String.class);
+                    return m.invoke(null, object.toString().toUpperCase());
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            } else if (object instanceof Number) {
+                try {
+                    Method m = targetCls.getMethod("values");
+                    Object[] values = (Object[]) m.invoke(null);
+                    return values[((Number) object).intValue()];
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+        }
+        return null;
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T tryStandardMethods(Class<T> cls) {
+        try {
+            Method m = cls.getDeclaredMethod("valueOf", String.class);
+            if (m != null) {
+                return (T) m.invoke(null, object.toString());
+            }
+        } catch (Exception e) {
+            try {
+                Constructor<T> ctr = cls.getConstructor(String.class);
+                return ctr.newInstance(object.toString());
+            } catch (Exception e2) {
+            }
+        }
+        return null;
+    }
+
+    private static Collection<?> collectionView(Object obj) {
+        if (obj == null)
+            return null;
+
+        Collection<?> c = asCollection(obj);
+        if (c == null)
+            return Collections.singleton(obj);
+        else
+            return c;
+    }
+
+    private static Collection<?> asCollection(Object obj) {
+        if (obj instanceof Collection)
+            return (Collection<?>) obj;
+        else if ((obj = asBoxedArray(obj)) instanceof Object[])
+            return Arrays.asList((Object[]) obj);
+        else
+            return null;
+    }
+
+    private static Object asBoxedArray(Object obj) {
+        Class<?> objClass = obj.getClass();
+        if (!objClass.isArray())
+            return obj;
+
+        int len = Array.getLength(obj);
+        Object arr = Array.newInstance(Util.primitiveToBoxed(objClass.getComponentType()), len);
+        for (int i=0; i<len; i++) {
+            Object val = Array.get(obj, i);
+            Array.set(arr, i, val);
+        }
+        return arr;
+    }
+
+    @SuppressWarnings("rawtypes")
+    private static Map createMapFromBeanAccessors(Object obj) {
+        Set<String> invokedMethods = new HashSet<>();
+
+        Map result = new HashMap();
+        for (Method md : obj.getClass().getDeclaredMethods()) {
+            handleBeanMethod(obj, md, invokedMethods, result);
+        }
+        for (Method md : obj.getClass().getMethods()) {
+            handleBeanMethod(obj, md, invokedMethods, result);
+        }
+
+        return result;
+    }
+
+    @SuppressWarnings("rawtypes")
+    private static Map createMapFromDTO(Object obj, Converter converter) {
+        Set<String> handledFields = new HashSet<>();
+
+        Map result = new HashMap();
+        for (Field f : obj.getClass().getDeclaredFields()) {
+            handleField(obj, f, handledFields, result, converter);
+        }
+        for (Field f : obj.getClass().getFields()) {
+            handleField(obj, f, handledFields, result, converter);
+        }
+        return result;
+    }
+
+    @SuppressWarnings("rawtypes")
+    private static Map createMapFromInterface(Object obj) {
+        Set<String> invokedMethods = new HashSet<>();
+
+        Map result = new HashMap();
+        for (Method md : obj.getClass().getDeclaredMethods()) {
+            handleInterfaceMethod(obj, md, invokedMethods, result);
+        }
+        for (Method md : obj.getClass().getMethods()) {
+            handleInterfaceMethod(obj, md, invokedMethods, result);
+        }
+
+        return result;
+    }
+
+    private static Object createMapOrCollection(Class<?> targetCls, int initialSize) {
+        try {
+            Constructor<?> ctor = targetCls.getConstructor(int.class);
+            return ctor.newInstance(initialSize);
+        } catch (Exception e1) {
+            try {
+                Constructor<?> ctor2 = targetCls.getConstructor();
+                return ctor2.newInstance();
+            } catch (Exception e2) {
+                e2.printStackTrace();
+            }
+        }
+        return null;
+    }
+
+    private static String getAccessorPropertyName(Method md) {
+        if (md.getReturnType().equals(Void.class))
+            return null; // not an accessor
+
+        if (md.getParameterTypes().length > 0)
+            return null; // not an accessor
+
+        if (Object.class.equals(md.getDeclaringClass()))
+            return null; // do not use any methods on the Object class as a accessor
+
+        String mn = md.getName();
+        int prefix;
+        if (mn.startsWith("get"))
+            prefix = 3;
+        else if (mn.startsWith("is"))
+            prefix = 2;
+        else
+            return null; // not an accessor prefix
+
+        if (mn.length() <= prefix)
+            return null; // just 'get' or 'is': not an accessor
+        String propStr = mn.substring(prefix);
+        StringBuilder propName = new StringBuilder(propStr.length());
+        char firstChar = propStr.charAt(0);
+        if (!Character.isUpperCase(firstChar))
+            return null; // no acccessor as no camel casing
+        propName.append(Character.toLowerCase(firstChar));
+        if (propStr.length() > 1)
+            propName.append(propStr.substring(1));
+
+        return propName.toString();
+    }
+
+    private static String getInterfacePropertyName(Method md) {
+        if (md.getReturnType().equals(Void.class))
+            return null; // not an accessor
+
+        if (md.getParameterTypes().length > 1)
+            return null; // not an accessor
+
+        if (Object.class.equals(md.getDeclaringClass()))
+            return null; // do not use any methods on the Object class as a accessor
+
+        return md.getName().replace('_', '.'); // TODO support all the escaping mechanisms.
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private static void handleField(Object obj, Field field, Set<String> handledFields, Map result, Converter converter) {
+        if (Modifier.isStatic(field.getModifiers()))
+            return;
+
+        String fn = field.getName();
+        if (handledFields.contains(fn))
+            return; // Field with this name was already handled
+
+        try {
+            Object fVal = field.get(obj);
+            if(fVal instanceof DTO)
+                fVal = converter.convert(fVal).to(Map.class);
+            // TODO test for other embedded types that need conversion
+            result.put(field.getName(), fVal);
+            handledFields.add(fn);
+        } catch (Exception e) {
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private static void handleBeanMethod(Object obj, Method md, Set<String> invokedMethods, Map res) {
+        if (Modifier.isStatic(md.getModifiers()))
+            return;
+
+        String mn = md.getName();
+        if (invokedMethods.contains(mn))
+            return; // method with this name already invoked
+
+        String propName = getAccessorPropertyName(md);
+        if (propName == null)
+            return;
+
+        try {
+            res.put(propName.toString(), md.invoke(obj));
+            invokedMethods.add(mn);
+        } catch (Exception e) {
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private static void handleInterfaceMethod(Object obj, Method md, Set<String> invokedMethods, Map res) {
+        if (Modifier.isStatic(md.getModifiers()))
+            return;
+
+        String mn = md.getName();
+        if (invokedMethods.contains(mn))
+            return; // method with this name already invoked
+
+        String propName = getInterfacePropertyName(md);
+        if (propName == null)
+            return;
+
+        try {
+            res.put(propName.toString(), md.invoke(obj));
+            invokedMethods.add(mn);
+        } catch (Exception e) {
+        }
+    }
+
+    private static Map<?,?> mapView(Object obj, Converter converter) {
+        if (obj instanceof Map)
+            return (Map<?,?>) obj;
+        else if (obj instanceof Dictionary)
+            return null; // TODO
+        else if (obj instanceof DTO)
+            return createMapFromDTO(obj, converter);
+        else if (obj.getClass().getInterfaces().length > 0)
+            return createMapFromInterface(obj);
+        else
+            return createMapFromBeanAccessors(obj);
+    }
+
+    private static boolean isWriteableJavaBean(Class<?> cls) {
+        boolean hasNoArgCtor = false;
+        for (Constructor<?> ctor : cls.getConstructors()) {
+            if (ctor.getParameterTypes().length == 0)
+                hasNoArgCtor = true;
+        }
+        if (!hasNoArgCtor)
+            return false; // A JavaBean must have a public no-arg constructor
+
+        return getSetters(cls).size() > 0;
+    }
+
+    private static Set<Method> getSetters(Class<?> cls) {
+        Set<Method> setters = new HashSet<>();
+        while (!Object.class.equals(cls)) {
+            Set<Method> methods = new HashSet<>();
+            methods.addAll(Arrays.asList(cls.getDeclaredMethods()));
+            methods.addAll(Arrays.asList(cls.getMethods()));
+            for (Method md : methods) {
+                if (md.getParameterTypes().length != 1)
+                    continue; // Only setters with a single argument
+                String name = md.getName();
+                if (name.length() < 4)
+                    continue;
+                if (name.startsWith("set") &&
+                        Character.isUpperCase(name.charAt(3)))
+                    setters.add(md);
+            }
+            cls = cls.getSuperclass();
+        }
+        return setters;
+    }
+
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/InternalConverter.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/InternalConverter.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/InternalConverter.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/InternalConverter.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,23 @@
+/*
+ * 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.converter.impl;
+
+import org.osgi.service.converter.Converter;
+
+public interface InternalConverter extends Converter {
+    public InternalConverting convert(Object obj);
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/InternalConverting.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/InternalConverting.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/InternalConverting.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/InternalConverting.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,24 @@
+/*
+ * 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.converter.impl;
+
+import org.osgi.service.converter.Converter;
+import org.osgi.service.converter.Converting;
+
+public interface InternalConverting extends Converting {
+    public void setConverter(Converter c);
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Util.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Util.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Util.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Util.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,82 @@
+/*
+ * 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.converter.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Util {
+    private static final Map<Class<?>, Class<?>> boxedClasses;
+    static {
+        Map<Class<?>, Class<?>> m = new HashMap<>();
+        m.put(int.class, Integer.class);
+        m.put(long.class, Long.class);
+        m.put(double.class, Double.class);
+        m.put(float.class, Float.class);
+        m.put(boolean.class, Boolean.class);
+        m.put(char.class, Character.class);
+        m.put(byte.class, Byte.class);
+        m.put(void.class, Void.class);
+        m.put(short.class, Short.class);
+        boxedClasses = Collections.unmodifiableMap(m);
+    }
+
+    static Type primitiveToBoxed(Type type) {
+        if (type instanceof Class)
+            return primitiveToBoxed((Class<?>) type);
+        else
+            return null;
+    }
+
+    static Class<?> primitiveToBoxed(Class<?> cls) {
+        Class<?> boxed = boxedClasses.get(cls);
+        if (boxed != null)
+            return boxed;
+        else
+            return cls;
+    }
+
+    public static byte [] readStream(InputStream is) throws IOException {
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            byte[] bytes = new byte[8192];
+
+            int length = 0;
+            int offset = 0;
+
+            while ((length = is.read(bytes, offset, bytes.length - offset)) != -1) {
+                offset += length;
+
+                if (offset == bytes.length) {
+                    baos.write(bytes, 0, bytes.length);
+                    offset = 0;
+                }
+            }
+            if (offset != 0) {
+                baos.write(bytes, 0, offset);
+            }
+            return baos.toByteArray();
+        } finally {
+            is.close();
+        }
+    }
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonCodecImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonCodecImpl.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonCodecImpl.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonCodecImpl.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,135 @@
+/*
+ * 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.converter.impl.json;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.felix.converter.impl.ConverterImpl;
+import org.osgi.service.converter.Codec;
+import org.osgi.service.converter.Converter;
+import org.osgi.service.converter.Decoding;
+import org.osgi.service.converter.Encoding;
+import org.osgi.service.converter.TypeReference;
+
+public class JsonCodecImpl implements Codec {
+    private Map<String, Object> configuration = new ConcurrentHashMap<>();
+    private ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
+    private Converter converter = new ConverterImpl();
+
+    @Override
+    public Codec with(Converter c) {
+        converter = c;
+        return this;
+    }
+
+    @Override
+    public <T> Decoding<T> decode(Class<T> cls) {
+        return new JsonDecodingImpl<T>(converter, cls);
+    }
+
+    @Override
+    public Encoding encode(Object obj) {
+        Encoding encoding = new JsonEncodingImpl(converter, configuration, obj);
+
+        if (pretty()) {
+            Boolean top = threadLocal.get();
+            if (top == null) {
+                threadLocal.set(Boolean.TRUE);
+
+                // TODO implement this properly, the following it just a dev temp thing
+                encoding = new EncodingWrapper("{}{}{}{}{}", encoding, "{}{}{}{}{}");
+            }
+        }
+        return encoding;
+    }
+
+    private boolean pretty() {
+        return Boolean.TRUE.equals(Boolean.parseBoolean((String) configuration.get("pretty")));
+    }
+
+    private class EncodingWrapper implements Encoding {
+        private final Encoding delegate;
+        private String prefix;
+        private String postfix;
+
+        EncodingWrapper(String pre, Encoding encoding, String post) {
+            prefix = pre;
+            delegate = encoding;
+            postfix = post;
+        }
+
+        @Override
+        public void to(OutputStream os) {
+            try {
+                os.write(toString().getBytes(StandardCharsets.UTF_8));
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public String toString() {
+            try {
+                return prefix + delegate.toString() + postfix;
+            } finally {
+                threadLocal.set(null);
+            }
+        }
+
+        @Override
+        public Encoding ignoreNull() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Encoding pretty() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public void to(OutputStream out, Charset charset) {
+            // TODO Auto-generated method stub
+
+        }
+
+        @Override
+        public Appendable to(Appendable out) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+    }
+
+    @Override
+    public <T> Decoding<T> decode(TypeReference<T> ref) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Decoding<?> decode(Type type) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonDecodingImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonDecodingImpl.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonDecodingImpl.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonDecodingImpl.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,74 @@
+/*
+ * 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.converter.impl.json;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Scanner;
+
+import org.apache.felix.converter.impl.Util;
+import org.osgi.service.converter.ConversionException;
+import org.osgi.service.converter.Converter;
+import org.osgi.service.converter.Decoding;
+
+public class JsonDecodingImpl<T> implements Decoding<T> {
+    private final Class<T> clazz;
+    private final Converter converter;
+
+    public JsonDecodingImpl(Converter c, Class<T> cls) {
+        converter = c;
+        clazz = cls;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public T from(CharSequence in) {
+        JsonParser jp = new JsonParser(in);
+        Map<?,?> m = jp.getParsed();
+        if (m.getClass().isAssignableFrom(clazz))
+            return (T) m;
+
+        return converter.convert(m).to(clazz);
+    }
+
+    @Override
+    public T from(InputStream in) {
+        return from(in, StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public T from(InputStream in, Charset charset) {
+        try {
+            byte[] bytes = Util.readStream(in);
+            String s = new String(bytes, charset);
+            return from(s);
+        } catch (IOException e) {
+            throw new ConversionException("Error reading inputstream", e);
+        }
+    }
+
+    @Override
+    public T from(Readable in) {
+        try (Scanner s = new Scanner(in)) {
+            s.useDelimiter("\\Z");
+            return from(s.next());
+        }
+    }
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonEncodingImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonEncodingImpl.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonEncodingImpl.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonEncodingImpl.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,160 @@
+/*
+ * 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.converter.impl.json;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Array;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.osgi.dto.DTO;
+import org.osgi.service.converter.ConversionException;
+import org.osgi.service.converter.Converter;
+import org.osgi.service.converter.Encoding;
+
+public class JsonEncodingImpl implements Encoding {
+    private final Converter converter;
+    private final Map<String, Object> configuration;
+    private final Object object;
+    private final boolean ignoreNull;
+
+    JsonEncodingImpl(Converter c, Map<String, Object> cfg, Object obj) {
+        converter = c;
+        configuration = cfg;
+        ignoreNull = Boolean.TRUE.equals(Boolean.parseBoolean((String) configuration.get("ignoreNull")));
+        object = obj;
+    }
+
+    @Override
+    public Appendable to(Appendable out) {
+        try {
+            out.append(encode(object));
+            return out;
+        } catch (IOException e) {
+            throw new ConversionException("Problem converting to JSON", e);
+        }
+    }
+
+    @Override
+    public void to(OutputStream os, Charset charset) {
+        try {
+            os.write(encode(object).getBytes(charset));
+        } catch (IOException e) {
+            throw new ConversionException("Problem converting to JSON", e);
+        }
+    }
+
+    @Override
+    public void to(OutputStream out) throws IOException {
+        to(out, StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public String toString() {
+        return encode(object);
+    }
+
+    @SuppressWarnings("rawtypes")
+    private String encode(Object obj) {
+        if (obj == null) {
+            return ignoreNull ? "" : "null";
+        }
+
+        if (obj instanceof Map) {
+            return encodeMap((Map) obj);
+        } else if (obj instanceof Collection) {
+            return encodeCollection((Collection) obj);
+        } else if (obj instanceof DTO) {
+            return encodeMap(converter.convert(obj).to(Map.class));
+        } else if (obj.getClass().isArray()) {
+            return encodeCollection(asCollection(obj));
+        } else if (obj instanceof Number) {
+            return obj.toString();
+        } else if (obj instanceof Boolean) {
+            return obj.toString();
+        }
+
+        return "\"" + converter.convert(obj).to(String.class) + "\"";
+    }
+
+    private Collection<?> asCollection(Object arr) {
+        // Arrays.asList() doesn't work for primitive arrays
+        int len = Array.getLength(arr);
+        List<Object> l = new ArrayList<>(len);
+        for (int i=0; i<len; i++) {
+            l.add(Array.get(arr, i));
+        }
+        return l;
+    }
+
+    private String encodeCollection(Collection<?> collection) {
+        StringBuilder sb = new StringBuilder("[");
+
+        boolean first = true;
+        for (Object o : collection) {
+            if (first)
+                first = false;
+            else
+                sb.append(',');
+
+            sb.append(encode(o));
+        }
+
+        sb.append("]");
+        return sb.toString();
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private String encodeMap(Map m) {
+        StringBuilder sb = new StringBuilder("{");
+        for (Entry entry : (Set<Entry>) m.entrySet()) {
+            if (entry.getKey() == null || entry.getValue() == null)
+                if (ignoreNull)
+                    continue;
+
+            if (sb.length() > 1)
+                sb.append(',');
+            sb.append('"');
+            sb.append(entry.getKey().toString());
+            sb.append("\":");
+            sb.append(encode(entry.getValue()));
+        }
+        sb.append("}");
+
+        return sb.toString();
+    }
+
+    @Override
+    public Encoding ignoreNull() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Encoding pretty() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonParser.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonParser.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonParser.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/json/JsonParser.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,253 @@
+/*
+ * 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.converter.impl.json;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.converter.impl.Util;
+
+/**
+ * A very small JSON parser.
+ *
+ * The JSON input is parsed into an object structure in the following way:
+ * <ul>
+ * <li>Object names are represented as a {@link String}.
+ * <li>String values are represented as a {@link String}.
+ * <li>Numeric values are represented as a {@link Long} (TODO support floats).
+ * <li>Boolean values are represented as a {@link Boolean}.
+ * <li>Nested JSON objects are parsed into a {@link java.util.Map Map&lt;String, Object&gt;}.
+ * <li>JSON lists are parsed into a {@link java.util.List} which may contain any of the above values.
+ * </ul>
+ */
+public class JsonParser {
+    private static final Pattern KEY_VALUE_PATTERN = Pattern.compile("^\\s*[\"](.+?)[\"]\\s*[:]\\s*(.+)$");
+
+    private enum Scope { QUOTE, CURLY, BRACKET;
+        static Scope getScope(char c) {
+            switch (c) {
+            case '"':
+                return QUOTE;
+            case '[':
+            case ']':
+                return BRACKET;
+            case '{':
+            case '}':
+                return CURLY;
+            default:
+                return null;
+            }
+        }
+    }
+
+    static class Pair<K, V> {
+        final K key;
+        final V value;
+
+        Pair(K k, V v) {
+            key = k;
+            value = v;
+        }
+    }
+
+    private final Map<String, Object> parsed;
+
+    public JsonParser(CharSequence json) {
+        String str = json.toString();
+        str = str.trim().replace('\n', ' ');
+        parsed = parseObject(str);
+    }
+
+    public JsonParser(InputStream is) throws IOException {
+        this(readStreamAsString(is));
+    }
+
+    public Map<String, Object> getParsed() {
+        return parsed;
+    }
+
+    private static Pair<String, Object> parseKeyValue(String jsonKeyValue) {
+        Matcher matcher = KEY_VALUE_PATTERN.matcher(jsonKeyValue);
+        if (!matcher.matches() || matcher.groupCount() < 2) {
+            throw new IllegalArgumentException("Malformatted JSON key-value pair: " + jsonKeyValue);
+        }
+
+        return new Pair<>(matcher.group(1), parseValue(matcher.group(2)));
+    }
+
+    private static Object parseValue(String jsonValue) {
+        jsonValue = jsonValue.trim();
+
+        switch (jsonValue.charAt(0)) {
+        case '\"':
+            if (!jsonValue.endsWith("\""))
+                throw new IllegalArgumentException("Malformatted JSON string: " + jsonValue);
+
+            return jsonValue.substring(1, jsonValue.length() - 1);
+        case '[':
+            List<Object> entries = new ArrayList<>();
+            for (String v : parseListValuesRaw(jsonValue)) {
+                entries.add(parseValue(v));
+            }
+            return entries;
+        case '{':
+            return parseObject(jsonValue);
+        case 't':
+        case 'T':
+        case 'f':
+        case 'F':
+            return Boolean.parseBoolean(jsonValue);
+        case 'n':
+        case 'N':
+            return null;
+        default:
+            return Long.parseLong(jsonValue);
+        }
+    }
+
+    private static Map<String, Object> parseObject(String jsonObject) {
+        if (!(jsonObject.startsWith("{") && jsonObject.endsWith("}")))
+            throw new IllegalArgumentException("Malformatted JSON object: " + jsonObject);
+
+        jsonObject = jsonObject.substring(1, jsonObject.length() - 1);
+        Map<String, Object> values = new HashMap<>();
+        for (String element : parseKeyValueListRaw(jsonObject)) {
+            Pair<String, Object> pair = parseKeyValue(element);
+            values.put(pair.key, pair.value);
+        }
+
+        return values;
+    }
+
+    private static List<String> parseKeyValueListRaw(String jsonKeyValueList) {
+        jsonKeyValueList = jsonKeyValueList + ","; // append comma to simplify parsing
+        List<String> elements = new ArrayList<>();
+
+        int i=0;
+        int start=0;
+        Stack<Scope> scopeStack = new Stack<>();
+        while (i < jsonKeyValueList.length()) {
+            char curChar = jsonKeyValueList.charAt(i);
+            switch (curChar) {
+            case '"':
+                if (i > 0 && jsonKeyValueList.charAt(i-1) == '\\') {
+                    // it's escaped, ignore for now
+                } else {
+                    if (!scopeStack.empty() && scopeStack.peek() == Scope.QUOTE) {
+                        scopeStack.pop();
+                    } else {
+                        scopeStack.push(Scope.QUOTE);
+                    }
+                }
+                break;
+            case '[':
+            case '{':
+                if ((scopeStack.empty() ? null : scopeStack.peek()) == Scope.QUOTE) {
+                    // inside quotes, ignore
+                } else {
+                    scopeStack.push(Scope.getScope(curChar));
+                }
+                break;
+            case ']':
+            case '}':
+                Scope curScope = scopeStack.empty() ? null : scopeStack.peek();
+                if (curScope == Scope.QUOTE) {
+                    // inside quotes, ignore
+                } else {
+                    Scope newScope = Scope.getScope(curChar);
+                    if (curScope == newScope) {
+                        scopeStack.pop();
+                    } else {
+                        throw new IllegalArgumentException("Unbalanced closing " +
+                            curChar + " in: " + jsonKeyValueList);
+                    }
+                }
+                break;
+            case ',':
+                if (scopeStack.empty()) {
+                    elements.add(jsonKeyValueList.substring(start, i));
+                    start = i+1;
+                }
+                break;
+            }
+
+            i++;
+        }
+        return elements;
+    }
+
+    private static List<String> parseListValuesRaw(String jsonList) {
+        if (!(jsonList.startsWith("[") && jsonList.endsWith("]")))
+            throw new IllegalArgumentException("Malformatted JSON list: " + jsonList);
+
+        jsonList = jsonList.substring(1, jsonList.length() - 1);
+        return parseKeyValueListRaw(jsonList);
+    }
+
+    private static String readStreamAsString(InputStream is) throws IOException {
+        byte [] bytes = Util.readStream(is);
+        if (bytes.length < 5)
+            // need at least 5 bytes to establish the encoding
+            throw new IllegalArgumentException("Malformatted JSON");
+
+        int offset = 0;
+        if ((bytes[0] == -1 && bytes[1] == -2)
+            || (bytes[0] == -2 && bytes[1] == -1)) {
+            // Skip UTF16/UTF32 Byte Order Mark (BOM)
+            offset = 2;
+        }
+
+        /* Infer the encoding as described in section 3 of http://www.ietf.org/rfc/rfc4627.txt
+         * which reads:
+         *   Encoding
+         *
+         *   JSON text SHALL be encoded in Unicode.  The default encoding is
+         *   UTF-8.
+         *
+         *   Since the first two characters of a JSON text will always be ASCII
+         *   characters [RFC0020], it is possible to determine whether an octet
+         *   stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking
+         *   at the pattern of nulls in the first four octets.
+         *
+         *         00 00 00 xx  UTF-32BE
+         *         00 xx 00 xx  UTF-16BE
+         *         xx 00 00 00  UTF-32LE
+         *         xx 00 xx 00  UTF-16LE
+         *         xx xx xx xx  UTF-8
+         */
+        String encoding;
+        if (bytes[offset + 2] == 0) {
+            if (bytes[offset + 1] != 0) {
+                encoding = "UTF-16";
+            } else {
+                encoding = "UTF-32";
+            }
+        } else if (bytes[offset + 1] == 0) {
+            encoding = "UTF-16";
+        } else {
+            encoding = "UTF-8";
+        }
+        return new String(bytes, encoding);
+    }
+}
\ No newline at end of file

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/yaml/YamlCodecImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/yaml/YamlCodecImpl.java?rev=1757677&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/yaml/YamlCodecImpl.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/yaml/YamlCodecImpl.java Thu Aug 25 12:58:22 2016
@@ -0,0 +1,61 @@
+/*
+ * 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.converter.impl.yaml;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.felix.converter.impl.ConverterImpl;
+import org.osgi.service.converter.Codec;
+import org.osgi.service.converter.Converter;
+import org.osgi.service.converter.Decoding;
+import org.osgi.service.converter.Encoding;
+import org.osgi.service.converter.TypeReference;
+
+public class YamlCodecImpl implements Codec {
+    private Map<String, Object> configuration = new ConcurrentHashMap<>();
+    private Converter converter = new ConverterImpl();
+
+    @Override
+    public Codec with(Converter c) {
+        converter = c;
+        return this;
+    }
+
+    @Override
+    public <T> Decoding<T> decode(Class<T> cls) {
+        return new YamlDecodingImpl<T>(converter, cls);
+    }
+
+    @Override
+    public <T> Decoding<T> decode(TypeReference<T> ref) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Decoding<?> decode(Type type) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Encoding encode(Object obj) {
+        return new YamlEncodingImpl(converter, configuration, obj);
+    }
+}