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<String, Object>}.
+ * <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);
+ }
+}