From 72ff85921c867319c438cc3403872ed26b72961e Mon Sep 17 00:00:00 2001 From: David Leangen Date: Thu, 29 Dec 2016 09:02:18 -0800 Subject: [PATCH 01/18] Update to allow the use of DTO.class to signal that an Object should be treated as a DTO. --- .../felix/converter/impl/ConvertingImpl.java | 3 +++ .../felix/converter/impl/ConverterTest.java | 26 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java index b2bb41001af..3b5be4657b6 100644 --- a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java +++ b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java @@ -448,6 +448,9 @@ private Object handleNull(Class cls) { } private static boolean isDTOType(Class cls) { + if (DTO.class.equals(cls)) + return true; + try { cls.getDeclaredConstructor(); } catch (NoSuchMethodException | SecurityException e) { diff --git a/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java b/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java index 32321e953d3..25fb1fd4d33 100644 --- a/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java +++ b/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java @@ -52,6 +52,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.osgi.dto.DTO; import org.osgi.util.converter.ConversionException; import org.osgi.util.converter.Converter; import org.osgi.util.converter.ConverterBuilder; @@ -603,7 +604,7 @@ public void testConversionPriority() { } @Test - public void testConvertAs1() { + public void testConvertAsInterface() { MyBean mb = new MyBean(); mb.intfVal = 17; mb.beanVal = "Hello"; @@ -613,7 +614,7 @@ public void testConvertAs1() { } @Test - public void testConvertAs2() { + public void testConvertAsBean() { MyBean mb = new MyBean(); mb.intfVal = 17; mb.beanVal = "Hello"; @@ -622,6 +623,14 @@ public void testConvertAs2() { converter.convert(mb).sourceAsBean().to(Map.class)); } + @Test + public void testConvertAsDTO() { + MyClass3 mc3 = new MyClass3(17); + + assertEquals(17, + converter.convert(mc3).sourceAs(DTO.class).to(Map.class).get("value")); + } + @Test public void testDTONameMangling() { Map m = new HashMap<>(); @@ -680,4 +689,17 @@ public String getValue() { return beanVal; } } + + static class MyClass3 { + public int value; + public String string = "String"; + + public MyClass3( int value ) { + this.value = value; + } + + public int value() { + return value; + } + } } From 31b92bec9f0f580c6847262b49942d755a2d3987 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Thu, 29 Dec 2016 09:07:58 -0800 Subject: [PATCH 02/18] A more restrictive (and thus better) approach. --- .../java/org/apache/felix/converter/impl/ConvertingImpl.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java index 3b5be4657b6..fa659e1cbbc 100644 --- a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java +++ b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java @@ -448,9 +448,6 @@ private Object handleNull(Class cls) { } private static boolean isDTOType(Class cls) { - if (DTO.class.equals(cls)) - return true; - try { cls.getDeclaredConstructor(); } catch (NoSuchMethodException | SecurityException e) { @@ -840,7 +837,7 @@ private Map mapView(Object obj, Class sourceCls, InternalConverter conve return (Map) obj; else if (Dictionary.class.isAssignableFrom(sourceCls)) return null; // TODO - else if (isDTOType(sourceCls)) + else if (isDTOType(sourceCls) || DTO.class.equals(sourceCls)) return createMapFromDTO(obj, converter); else { if (sourceAsJavaBean) { From 12ace7f9c466853c08de8f43bb450ac34ab282e2 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Mon, 2 Jan 2017 20:09:27 -0800 Subject: [PATCH 03/18] Updates to get Schematizer working again. Based on working version of sourceAs(DTO.class). Signed-off-by: David Leangen --- .../felix/converter/impl/ConvertingImpl.java | 15 ++++- .../felix/converter/impl/ConverterTest.java | 27 ++++++++ .../felix/schematizer/Schematizing.java | 24 +++++++ .../schematizer/SchematizingConverter.java | 53 ++++++++++++++++ .../SchematizingConverterBuilderImpl.java | 62 +++++++++++++++++++ .../impl/SchematizingConverterImpl.java | 55 ++++++++++++++++ .../impl/json/JsonDeserializingImpl.java | 13 ++-- .../serializer/impl/json/JsonParser.java | 4 ++ .../impl/json/JsonSerializerImpl.java | 3 +- .../impl/json/JsonSerializingImpl.java | 2 +- .../impl/json/JsonDeserializationTest.java | 14 ++--- .../json/RepositorySerializationTest.java | 8 --- .../test/objects/provider/ObjectFactory.java | 3 +- .../test/prevayler/DTOSerializer.java | 9 +-- 14 files changed, 259 insertions(+), 33 deletions(-) create mode 100644 converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizing.java create mode 100644 converter/schematizer/src/main/java/org/apache/felix/schematizer/SchematizingConverter.java create mode 100644 converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterBuilderImpl.java create mode 100644 converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterImpl.java diff --git a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java index fa659e1cbbc..8b6f449c07e 100644 --- a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java +++ b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java @@ -189,7 +189,7 @@ public Object to(Type type) { return convertToArray(); } else if (Collection.class.isAssignableFrom(targetAsClass)) { return convertToCollection(); - } else if (isDTOType(targetAsClass)) { + } else if (isDTOType(targetAsClass) || (DTO.class.equals(sourceClass) && DTO.class.isAssignableFrom(targetActualClass))) { return convertToDTO(); } else if (isMapType(targetAsClass)) { return convertToMapType(); @@ -296,7 +296,11 @@ private T convertToDTO() { if (f != null) { Object val = entry.getValue(); - f.set(dto, converter.convert(val).to(f.getType())); + if (DTO.class.equals(sourceClass) && DTO.class.isAssignableFrom(f.getType())) + val = converter.convert(val).sourceAs(DTO.class).to(f.getType()); + else + val = converter.convert(val).to(f.getType()); + f.set(dto, val); } } @@ -342,7 +346,10 @@ private Map convertToMap() { if (isCopyRequiredType(cls)) { cls = getConstructableType(cls); } - value = converter.convert(value).key(ka).to(cls); + if (DTO.class.equals(sourceAsClass) && DTO.class.isAssignableFrom(cls)) + value = converter.convert(value).key(ka).sourceAs(sourceAsClass).to(cls); + else + value = converter.convert(value).key(ka).to(cls); } } instance.put(key, value); @@ -850,6 +857,8 @@ else if (isDTOType(sourceCls) || DTO.class.equals(sourceCls)) } private static boolean isCopyRequiredType(Class cls) { + if (cls.isEnum()) + return false; return Map.class.isAssignableFrom(cls) || Collection.class.isAssignableFrom(cls) || DTO.class.isAssignableFrom(cls) || diff --git a/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java b/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java index 25fb1fd4d33..b33b4110118 100644 --- a/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java +++ b/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java @@ -463,6 +463,33 @@ public void testDTO2Map() { assertEquals(Alpha.A, e.get("alpha")); } + @Test + public void testDTO2Map2() { + MyEmbeddedDTO embedded = new MyEmbeddedDTO(); + embedded.marco = "hohoho"; + embedded.polo = Long.MAX_VALUE; + embedded.alpha = Alpha.A; + + MyDTO dto = new MyDTO(); + dto.ping = "lalala"; + dto.pong = Long.MIN_VALUE; + dto.count = Count.ONE; + dto.embedded = embedded; + + @SuppressWarnings("rawtypes") + Map m = converter.convert(dto).sourceAs(DTO.class).to(Map.class); + assertEquals(4, m.size()); + assertEquals("lalala", m.get("ping")); + assertEquals(Long.MIN_VALUE, m.get("pong")); + assertEquals(Count.ONE, m.get("count")); + assertNotNull(m.get("embedded")); + @SuppressWarnings("rawtypes") + Map e = (Map)m.get("embedded"); + assertEquals("hohoho", e.get("marco")); + assertEquals(Long.MAX_VALUE, e.get("polo")); + assertEquals(Alpha.A, e.get("alpha")); + } + @Test @SuppressWarnings({ "rawtypes", "unchecked" }) public void testDTOFieldShadowing() { MySubDTO dto = new MySubDTO(); diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizing.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizing.java new file mode 100644 index 00000000000..a4161a94bab --- /dev/null +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizing.java @@ -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.schematizer; + +import org.osgi.util.converter.Converter; + +public interface Schematizing { + Converter withSchema(Schema s); + Schema getSchema(); +} diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/SchematizingConverter.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/SchematizingConverter.java new file mode 100644 index 00000000000..bf526fc5adc --- /dev/null +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/SchematizingConverter.java @@ -0,0 +1,53 @@ +/* + * 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.schematizer; + +import org.apache.felix.schematizer.impl.SchematizingConverterImpl; +import org.osgi.util.converter.Converter; +import org.osgi.util.converter.ConverterBuilder; +import org.osgi.util.converter.Converting; +import org.osgi.util.converter.StandardConverter; + +public class SchematizingConverter implements Schematizing, Converter { + private final SchematizingConverterImpl converter; + + public SchematizingConverter() { + converter = new SchematizingConverterImpl(new StandardConverter()); + } + + public SchematizingConverter(Converter c) { + converter = new SchematizingConverterImpl(c); + } + + @Override + public Converting convert(Object obj) { + return converter.convert(obj); + } + + @Override + public ConverterBuilder newConverterBuilder() { + return converter.newConverterBuilder(); + } + + public Converter withSchema(Schema s) { + return converter.withSchema(s); + } + + public Schema getSchema() { + return converter.getSchema(); + } +} diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterBuilderImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterBuilderImpl.java new file mode 100644 index 00000000000..70bdd7d3896 --- /dev/null +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterBuilderImpl.java @@ -0,0 +1,62 @@ +/* + * 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.schematizer.impl; + +import java.lang.reflect.Type; + +import org.osgi.util.converter.Converter; +import org.osgi.util.converter.ConverterBuilder; +import org.osgi.util.converter.Rule; +import org.osgi.util.converter.StandardConverter; +import org.osgi.util.converter.TypeReference; +import org.osgi.util.function.Function; + +public class SchematizingConverterBuilderImpl implements ConverterBuilder { + + private final ConverterBuilder builder = new StandardConverter().newConverterBuilder(); + + @Override + public Converter build() + { + Converter converter = builder.build(); + return new SchematizingConverterImpl(converter); + } + + @Override + public ConverterBuilder rule( Rule rule ) + { + return builder.rule(rule); + } + + @Override + public ConverterBuilder rule( Class fromCls, Class toCls, Function toFun, Function fromFun ) + { + return builder.rule(fromCls, toCls, toFun, fromFun); + } + + @Override + public ConverterBuilder rule( TypeReference fromRef, TypeReference toRef, Function toFun, Function fromFun ) + { + return builder.rule(fromRef, toRef, toFun, fromFun); + } + + @Override + public ConverterBuilder rule( Type fromType, Type toType, Function toFun, Function fromFun ) + { + return builder.rule(fromType, toType, toFun, fromFun); + } +} diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterImpl.java new file mode 100644 index 00000000000..97ca1535763 --- /dev/null +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterImpl.java @@ -0,0 +1,55 @@ +/* + * 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.schematizer.impl; + +import org.apache.felix.schematizer.Schema; +import org.apache.felix.schematizer.Schematizing; +import org.apache.felix.schematizer.SchematizingConverter; +import org.osgi.util.converter.Converter; +import org.osgi.util.converter.ConverterBuilder; +import org.osgi.util.converter.Converting; +import org.osgi.util.converter.StandardConverter; + +public class SchematizingConverterImpl implements Schematizing, Converter { + + private final Converter converter; + private Schema schema; + + public SchematizingConverterImpl(Converter c) + { + converter = c; + } + + @Override + public Converting convert(Object obj) { + return converter.convert(obj); + } + + @Override + public ConverterBuilder newConverterBuilder() { + return new SchematizingConverterBuilderImpl(); + } + + public Converter withSchema(Schema s) { + schema = s; + return this; + } + + public Schema getSchema() { + return schema; + } +} diff --git a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonDeserializingImpl.java b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonDeserializingImpl.java index 4fa0e415e53..f2cc38331c7 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonDeserializingImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonDeserializingImpl.java @@ -31,6 +31,8 @@ import org.apache.felix.schematizer.Node; import org.apache.felix.schematizer.Schema; +import org.apache.felix.schematizer.Schematizing; +import org.apache.felix.schematizer.SchematizingConverter; import org.apache.felix.schematizer.impl.Util; import org.osgi.dto.DTO; import org.osgi.service.serializer.Deserializing; @@ -52,13 +54,8 @@ public JsonDeserializingImpl(Converter c, Object t) { public JsonDeserializingImpl with(Converter c) { converter = c; - return this; - } - - public JsonDeserializingImpl withContext(Object obj) - { - if(obj instanceof Schema) - schema = (Schema)obj; + if(converter instanceof Schematizing) + schema = ((Schematizing)converter).getSchema(); return this; } @@ -144,6 +141,8 @@ private U convertToDTO(Class targetCls, Map m, Schema sch try { Field f = targetCls.getField(entry.getKey().toString()); Object val = entry.getValue(); + if (val == null) + continue; String path = contextPath + f.getName(); Optional opt = schema.nodeAtPath(path); if (opt.isPresent()) { diff --git a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java index c45e10d5cad..4a15a1047a5 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java +++ b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java @@ -131,6 +131,10 @@ private static Map parseObject(String jsonObject) { throw new IllegalArgumentException("Malformatted JSON object: " + jsonObject); jsonObject = jsonObject.substring(1, jsonObject.length() - 1); + + if (jsonObject.isEmpty()) + return null; + Map values = new HashMap<>(); for (String element : parseKeyValueListRaw(jsonObject)) { Pair pair = parseKeyValue(element); diff --git a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonSerializerImpl.java b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonSerializerImpl.java index 1a3c5f66546..a75d8c4cc7a 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonSerializerImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonSerializerImpl.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.apache.felix.schematizer.SchematizingConverter; import org.osgi.service.serializer.Serializer; import org.osgi.service.serializer.Serializing; import org.osgi.util.converter.Converter; @@ -33,7 +34,7 @@ public class JsonSerializerImpl implements Serializer { private final Map configuration = new ConcurrentHashMap<>(); private final ThreadLocal threadLocal = new ThreadLocal<>(); - private final Converter converter = new StandardConverter(); + private final SchematizingConverter converter = new SchematizingConverter(); @Override public JsonDeserializingImpl deserialize(Class cls) { diff --git a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonSerializingImpl.java b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonSerializingImpl.java index 38dba82fc50..a6346a1ac5d 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonSerializingImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonSerializingImpl.java @@ -86,7 +86,7 @@ private String encode(Object obj) { } else if (obj instanceof Collection) { return encodeCollection((Collection) obj); } else if (obj instanceof DTO) { - return encodeMap(converter.convert(obj).to(Map.class)); + return encodeMap(converter.convert(obj).sourceAs(DTO.class).to(Map.class)); } else if (obj.getClass().isArray()) { return encodeCollection(asCollection(obj)); } else if (obj instanceof Number) { diff --git a/converter/schematizer/src/test/java/org/apache/felix/serializer/impl/json/JsonDeserializationTest.java b/converter/schematizer/src/test/java/org/apache/felix/serializer/impl/json/JsonDeserializationTest.java index 1a786c52af5..096986208f9 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/serializer/impl/json/JsonDeserializationTest.java +++ b/converter/schematizer/src/test/java/org/apache/felix/serializer/impl/json/JsonDeserializationTest.java @@ -20,6 +20,7 @@ import java.util.Optional; import org.apache.felix.schematizer.Schema; +import org.apache.felix.schematizer.SchematizingConverter; import org.apache.felix.schematizer.impl.SchematizerImpl; import org.apache.felix.serializer.impl.json.MyDTO.Count; import org.apache.felix.serializer.impl.json.MyEmbeddedDTO.Alpha; @@ -34,11 +35,11 @@ import static org.junit.Assert.assertTrue; public class JsonDeserializationTest { - private Converter converter; + private SchematizingConverter converter; @Before public void setUp() { - converter = new StandardConverter(); + converter = new SchematizingConverter(); } @After @@ -59,8 +60,6 @@ public void testDeserialization() { dto.count = Count.TWO; dto.embedded = embedded; - String serialized = new JsonSerializerImpl().serialize(dto).toString(); - // TODO Optional opt = new SchematizerImpl() .rule("MyDTO", new TypeReference(){}) @@ -71,10 +70,10 @@ public void testDeserialization() { Schema s = opt.get(); + String serialized = new JsonSerializerImpl().serialize(dto).with(converter.withSchema(s)).toString(); MyDTO result = new JsonSerializerImpl() .deserialize(MyDTO.class) - .with(converter) - .withContext(s) + .with(converter.withSchema(s)) .from(serialized); assertEquals(dto.ping, result.ping); @@ -110,8 +109,7 @@ public void testDeserializationWithCollection() { MyDTO2> result = new JsonSerializerImpl() .deserialize(new TypeReference>>(){}) - .with(converter) - .withContext(s) + .with(converter.withSchema(s)) .from(serialized); assertEquals(dto.ping, result.ping); diff --git a/converter/schematizer/src/test/java/org/apache/felix/serializer/impl/json/RepositorySerializationTest.java b/converter/schematizer/src/test/java/org/apache/felix/serializer/impl/json/RepositorySerializationTest.java index e896736d078..124cb4c3157 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/serializer/impl/json/RepositorySerializationTest.java +++ b/converter/schematizer/src/test/java/org/apache/felix/serializer/impl/json/RepositorySerializationTest.java @@ -74,7 +74,6 @@ public void cleanup() throws Exception { } @Test - @Ignore("davidb: I've ignore'd this test as it fails. Need to revisit once the converter has stabilized") public void shouldPutAndRemoveSimpleEntitiesFromStore() { simpleManager.clear(); final SimpleTopEntity e1 = factory.newSimpleTop( "ID01", "Value01", null ); @@ -115,9 +114,6 @@ public void shouldPutAndRemoveSimpleEntitiesFromStore() { } @Test - @Ignore("davidb: I've @Ignore-d this test as the DTO used breaks the DTO " - + "contract which says that DTOs cannot contain any methods. As these " - + "entities contain some methods they are not recognized as DTOs") public void shouldPutAndRemoveComplexEntityFromStore() { complexManager.clear(); assertTrue( complexManager.list().isEmpty() ); @@ -158,9 +154,6 @@ public void shouldPutAndRemoveComplexEntityFromStore() { } @Test - @Ignore("davidb: I've @Ignore-d this test as the DTO used breaks the DTO " - + "contract which says that DTOs cannot contain any methods. As these " - + "entities contain some methods they are not recognized as DTOs") public void shouldPutAllToStore() { complexManager.clear(); assertTrue( complexManager.list().isEmpty() ); @@ -211,7 +204,6 @@ public void shouldPutAllToStore() { } @Test - @Ignore("davidb: I've ignore'd this test as it fails. Need to revisit once the converter has stabilized") public void shouldIterateThroughKeysAndValues() { simpleManager.clear(); diff --git a/converter/schematizer/src/test/java/org/apache/felix/serializer/test/objects/provider/ObjectFactory.java b/converter/schematizer/src/test/java/org/apache/felix/serializer/test/objects/provider/ObjectFactory.java index 46ce95b47d7..5f10b6386d3 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/serializer/test/objects/provider/ObjectFactory.java +++ b/converter/schematizer/src/test/java/org/apache/felix/serializer/test/objects/provider/ObjectFactory.java @@ -18,6 +18,7 @@ import java.util.Collection; +import org.osgi.dto.DTO; import org.osgi.util.converter.Converter; import org.osgi.util.converter.StandardConverter; @@ -30,7 +31,7 @@ public ComplexTopEntity newComplexTop( String anId, String aValue, ComplexMiddle final ComplexTopEntity top = new ComplexTopEntity(); top.id = anId; top.value = aValue; - top.embeddedValue = cnv.convert( aMiddle ).to( ComplexMiddleEntity.class ); + top.embeddedValue = cnv.convert( aMiddle ).sourceAs(DTO.class).to( ComplexMiddleEntity.class ); return top; } diff --git a/converter/schematizer/src/test/java/org/apache/felix/serializer/test/prevayler/DTOSerializer.java b/converter/schematizer/src/test/java/org/apache/felix/serializer/test/prevayler/DTOSerializer.java index eb775bc13a2..963873e74ef 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/serializer/test/prevayler/DTOSerializer.java +++ b/converter/schematizer/src/test/java/org/apache/felix/serializer/test/prevayler/DTOSerializer.java @@ -28,6 +28,7 @@ import org.apache.felix.schematizer.Schema; import org.apache.felix.schematizer.Schematizer; +import org.apache.felix.schematizer.SchematizingConverter; import org.apache.felix.schematizer.TypeRule; import org.apache.felix.schematizer.impl.SchematizerImpl; import org.apache.felix.serializer.impl.json.JsonSerializerImpl; @@ -39,7 +40,7 @@ public class DTOSerializer> { private static final int MARKER_LENGTH = 10; - private final Converter converter = new StandardConverter(); + private final SchematizingConverter converter = new SchematizingConverter(); private final JsonSerializerImpl serializer = new JsonSerializerImpl(); private final List> rules; private final Map schemas = new HashMap<>(); @@ -62,8 +63,7 @@ public C deserialize( InputStream in ) Schema s = schemas.get( command.name() ); return (C)serializer .deserialize( CommandDTO.class ) - .with( converter ) - .withContext( s ) + .with( converter.withSchema( s ) ) .from( in ); } @@ -96,7 +96,8 @@ public Type getType() } out.write( markerFor( command.command ) ); - serializer.serialize( command ).to( out ); + Schema s = schemas.get( name ); + serializer.serialize( command ).with( converter.withSchema( s ) ).to( out ); } private final byte[] markerFor( Command command ) From d59a3e9152122d1abd9dc7a2b80fe04f942c17bf Mon Sep 17 00:00:00 2001 From: David Leangen Date: Tue, 3 Jan 2017 06:22:06 -0800 Subject: [PATCH 04/18] A few additional API updates to the Schematizer Signed-off-by: David Leangen --- .../main/java/org/apache/felix/schematizer/Node.java | 1 + .../java/org/apache/felix/schematizer/Schematizer.java | 2 ++ .../apache/felix/schematizer/StandardSchematizer.java | 5 +++++ .../apache/felix/schematizer/impl/SchematizerImpl.java | 10 ++++++++++ 4 files changed, 18 insertions(+) diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Node.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Node.java index 4831828446f..ca74758b723 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Node.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Node.java @@ -39,6 +39,7 @@ public static class DTO extends org.osgi.dto.DTO { public String name; public String path; public String type; + public String collectionType; public boolean isCollection; public Map children = new HashMap<>(); } diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizer.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizer.java index cf94c912db2..efc71f0ddc4 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizer.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizer.java @@ -35,4 +35,6 @@ public interface Schematizer { * Shortcut for rule(String name, String path, TypeReference type) when path is "/". */ Schematizer rule(String name, TypeReference type); + + Schematizer usingLookup(ClassLoader classLoader); } diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/StandardSchematizer.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/StandardSchematizer.java index 677dda161d6..8a0514c981d 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/StandardSchematizer.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/StandardSchematizer.java @@ -58,4 +58,9 @@ public Schematizer rule(String name, String path, Class cls) { public Schematizer rule(String name, TypeReference type) { return schematizer.rule(name, type); } + + @Override + public Schematizer usingLookup(ClassLoader classLoader) { + return schematizer.usingLookup(classLoader); + } } diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java index 98d85f3b2ff..89bb15cbda2 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java @@ -20,12 +20,14 @@ import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; @@ -45,6 +47,7 @@ public class SchematizerImpl implements Schematizer { private final Map schemas = new HashMap<>(); private volatile Map> typeRules = new HashMap<>(); + private final List classloaders = new ArrayList<>(); @Override public Optional get(String name) { @@ -107,6 +110,13 @@ private Map rulesFor(String name) { return typeRules.get(name); } + @Override + public Schematizer usingLookup( ClassLoader classloader ) { + if (classloader != null) + classloaders.add(classloader); + return this; + } + /** * Top-level entry point for schematizing a DTO. This is the starting point to set up the * parsing. All other methods make recursive calls. From d368f2d24c113b927f0b66186eb296dd18829fa3 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Tue, 3 Jan 2017 15:40:22 -0800 Subject: [PATCH 05/18] Additional updates to Schematizer Signed-off-by: David Leangen --- .../felix/schematizer/Schematizing.java | 2 + .../schematizer/SchematizingConverter.java | 11 ++++ .../schematizer/impl/CollectionNode.java | 20 +++++++ .../felix/schematizer/impl/NodeImpl.java | 19 ++++-- .../schematizer/impl/SchematizerImpl.java | 58 +++++++++++++------ .../impl/SchematizingConverterImpl.java | 12 ++++ .../impl/json/JsonDeserializingImpl.java | 15 ++++- .../serializer/impl/json/JsonParser.java | 5 +- 8 files changed, 114 insertions(+), 28 deletions(-) diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizing.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizing.java index a4161a94bab..aca1f8e5d91 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizing.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schematizing.java @@ -19,6 +19,8 @@ import org.osgi.util.converter.Converter; public interface Schematizing { + Converter asDTO(); + boolean isDTOType(); Converter withSchema(Schema s); Schema getSchema(); } diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/SchematizingConverter.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/SchematizingConverter.java index bf526fc5adc..7fb4fadb88c 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/SchematizingConverter.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/SchematizingConverter.java @@ -43,6 +43,17 @@ public ConverterBuilder newConverterBuilder() { return converter.newConverterBuilder(); } + @Override + public Converter asDTO() { + converter.asDTO(); + return converter; + } + + @Override + public boolean isDTOType() { + return converter.isDTOType(); + } + public Converter withSchema(Schema s) { return converter.withSchema(s); } diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/CollectionNode.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/CollectionNode.java index 13d666b939b..91e9d22c9de 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/CollectionNode.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/CollectionNode.java @@ -17,7 +17,10 @@ import java.lang.reflect.Type; import java.util.Collection; +import java.util.Map; +import java.util.function.Function; +import org.apache.felix.schematizer.Node; import org.osgi.util.converter.TypeReference; public class CollectionNode @@ -43,8 +46,25 @@ public CollectionNode( collectionType = aCollectionType; } + public CollectionNode( + Node.DTO dto, + String contextPath, + Function f, + Map nodes, + Class> aCollectionType ) { + super(dto, contextPath, f, nodes); + collectionType = aCollectionType; + } + @Override public Class> collectionType() { return collectionType; } + + @Override + public DTO toDTO() { + DTO dto = super.toDTO(); + dto.collectionType = collectionType.getName(); + return dto; + } } diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/NodeImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/NodeImpl.java index 2b270fb48b6..e05f65d1c17 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/NodeImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/NodeImpl.java @@ -57,16 +57,25 @@ public NodeImpl( absolutePath = anAbsolutePath; } + @SuppressWarnings( { "unchecked", "rawtypes" } ) public NodeImpl(Node.DTO dto, String contextPath, Function f, Map nodes) { name = dto.name; type = f.apply(dto.type); isCollection = dto.isCollection; absolutePath = contextPath + dto.path; - dto.children.values().stream().forEach( c -> { - NodeImpl node = new NodeImpl(c, contextPath, f, nodes); - children.put("/" + c.name, node); - nodes.put(c.path, node); - }); + for (Node.DTO child : dto.children.values()) { + NodeImpl node; + if (child.isCollection) + try { + node = new CollectionNode(child, contextPath, f, nodes, (Class)getClass().getClassLoader().loadClass(child.collectionType)); + } catch ( ClassNotFoundException e ) { + node = new CollectionNode(child, contextPath, f, nodes, (Class)Collection.class); + } + else + node = new NodeImpl(child, contextPath, f, nodes); + children.put("/" + child.name, node); + nodes.put(child.path, node); + } } @Override diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java index 89bb15cbda2..7cf59d30310 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java @@ -41,6 +41,7 @@ import org.apache.felix.schematizer.Schematizer; import org.apache.felix.schematizer.TypeRule; import org.osgi.dto.DTO; +import org.osgi.util.converter.StandardConverter; import org.osgi.util.converter.TypeReference; public class SchematizerImpl implements Schematizer { @@ -64,9 +65,10 @@ public Optional from(String name, Map map) { try { // TODO: some validation of the Map here would be good SchemaImpl schema = new SchemaImpl(name); - Node.DTO rootDTO = map.get("/"); + Object rootMap = map.get("/"); + Node.DTO rootDTO = new StandardConverter().convert( rootMap ).to( Node.DTO.class ); Map allNodes = new HashMap<>(); - NodeImpl root = new NodeImpl(rootDTO, "", instantiator, allNodes); + NodeImpl root = new NodeImpl(rootDTO, "", new Instantiator(classloaders), allNodes); schema.add(root); schema.add(allNodes); return Optional.of(schema); @@ -271,9 +273,7 @@ else if (fieldRef != null ) Map allNodes = embedded.toMapInternal(); allNodes.remove(path + "/"); result.putAll(allNodes); - Map childNodes = new HashMap<>(); - allNodes.keySet().stream() - .forEach( k -> {String k2 = k.replace(path, ""); childNodes.put(k2, allNodes.get(k));} ); + Map childNodes = extractChildren(path, allNodes); node.add(childNodes); } else { Type fieldType = field.getType(); @@ -302,9 +302,7 @@ else if (fieldRef != null ) Map allNodes = embedded.toMapInternal(); allNodes.remove(path + "/"); result.putAll(allNodes); - Map childNodes = new HashMap<>(); - allNodes.keySet().stream() - .forEach( k -> {String k2 = k.replace(path, ""); childNodes.put(k2, allNodes.get(k));} ); + Map childNodes = extractChildren(path, allNodes); node.add(childNodes); } } @@ -322,9 +320,7 @@ else if (DTO.class.isAssignableFrom(fieldClass)) { Map allNodes = embedded.toMapInternal(); allNodes.remove(path + "/"); result.putAll(allNodes); - Map childNodes = new HashMap<>(); - allNodes.keySet().stream() - .forEach( k -> {String k2 = k.replace(path, ""); childNodes.put(k2, allNodes.get(k));} ); + Map childNodes = extractChildren(path, allNodes); node.add(childNodes); } else { node = new NodeImpl( @@ -344,6 +340,17 @@ else if (DTO.class.isAssignableFrom(fieldClass)) { } } + private static Map extractChildren( String path, Map allNodes ) { + final Map children = new HashMap<>(); + for (String key : allNodes.keySet()) { + String newKey = key.replace(path, ""); + if (!newKey.substring(1).contains("/")) + children.put( newKey, allNodes.get(key)); + } + + return children; + } + private static SchemaImpl handleInvalid() { // TODO return null; @@ -372,19 +379,32 @@ private static TypeReference typeReferenceOf(Object type) { return typeRef; } - /** - * In an OSGi environment, this is too naive, and will quickly break down. - * Consider it more as a placeholder for now. - */ - private static final Instantiator instantiator = new Instantiator(); public static class Instantiator implements Function { + private final List classloaders = new ArrayList<>(); + + public Instantiator(List aClassLoadersList) { + classloaders.addAll( aClassLoadersList ); + } + @Override public Type apply(String className) { + for (ClassLoader cl : classloaders) { + try { + return cl.loadClass(className); + } catch (ClassNotFoundException e) { + // Try next + } + } + + // Could not find the class. Try "this" ClassLoader try { - return Class.forName(className); - } catch ( ClassNotFoundException e ) { - return Object.class; + return getClass().getClassLoader().loadClass(className); + } catch (ClassNotFoundException e) { + // Too bad } + + // Nothing to do. Return Object.class as the fallback + return Object.class; } } } diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterImpl.java index 97ca1535763..42eb62ec182 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizingConverterImpl.java @@ -28,6 +28,7 @@ public class SchematizingConverterImpl implements Schematizing, Converter { private final Converter converter; private Schema schema; + private boolean asDTO = false; public SchematizingConverterImpl(Converter c) { @@ -44,6 +45,17 @@ public ConverterBuilder newConverterBuilder() { return new SchematizingConverterBuilderImpl(); } + @Override + public Converter asDTO() { + asDTO = true; + return converter; + } + + @Override + public boolean isDTOType() { + return asDTO; + } + public Converter withSchema(Schema s) { schema = s; return this; diff --git a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonDeserializingImpl.java b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonDeserializingImpl.java index f2cc38331c7..26e1ae2911d 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonDeserializingImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonDeserializingImpl.java @@ -44,6 +44,7 @@ public class JsonDeserializingImpl implements Deserializing { private final Object target; private Converter converter; private Schema schema; + private boolean asDTO = false; public JsonDeserializingImpl(Converter c, Object t) { converter = c; @@ -54,8 +55,11 @@ public JsonDeserializingImpl(Converter c, Object t) { public JsonDeserializingImpl with(Converter c) { converter = c; - if(converter instanceof Schematizing) - schema = ((Schematizing)converter).getSchema(); + if(converter instanceof Schematizing) { + Schematizing s = (Schematizing)converter; + schema = s.getSchema(); + asDTO = s.isDTOType(); + } return this; } @@ -70,7 +74,10 @@ public T from(CharSequence in) { if (schema != null) return deserialize(m); - return converter.convert(m).to(clazz); + if (asDTO) + return converter.convert(m).targetAs(DTO.class).to(clazz); + else + return converter.convert(m).to(clazz); } @Override @@ -210,6 +217,8 @@ private >V convertToCollection(Class targetCls, Cl @SuppressWarnings( "unchecked" ) private >V instantiateCollection(Class collectionClass) { + if (collectionClass == null) + return (V)new ArrayList(); if (Collection.class.equals(collectionClass) || List.class.isAssignableFrom(collectionClass)) return (V)new ArrayList(); else diff --git a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java index 4a15a1047a5..a0e149aed66 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java +++ b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java @@ -122,7 +122,10 @@ private static Object parseValue(String jsonValue) { case 'N': return null; default: - return Long.parseLong(jsonValue); + if (jsonValue.contains(".")) + return Double.parseDouble(jsonValue); + else + return Long.parseLong(jsonValue); } } From 1142979dcf15b20ada2cfde49c0c7e845258da24 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Tue, 3 Jan 2017 07:13:01 -0800 Subject: [PATCH 06/18] Should be more lenient with trailing commas (at least until there is a proper reporting mechanism, as debugging is too difficult) Signed-off-by: David Leangen --- .../felix/serializer/impl/json/JsonParser.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java index a0e149aed66..23b7f0ef738 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java +++ b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -122,10 +123,7 @@ private static Object parseValue(String jsonValue) { case 'N': return null; default: - if (jsonValue.contains(".")) - return Double.parseDouble(jsonValue); - else - return Long.parseLong(jsonValue); + return Long.parseLong(jsonValue); } } @@ -148,7 +146,11 @@ private static Map parseObject(String jsonObject) { } private static List parseKeyValueListRaw(String jsonKeyValueList) { - jsonKeyValueList = jsonKeyValueList + ","; // append comma to simplify parsing + if (jsonKeyValueList.trim().isEmpty()) + return Collections.emptyList(); + // Append comma to simplify parsing, if there is not already a trailing comma + if (!jsonKeyValueList.endsWith(",")) + jsonKeyValueList = jsonKeyValueList + ","; List elements = new ArrayList<>(); int i=0; From b6cb7c20144ad92c90b561c05c91a9a6f2abdb6d Mon Sep 17 00:00:00 2001 From: David Leangen Date: Tue, 3 Jan 2017 15:40:11 -0800 Subject: [PATCH 07/18] More fixes to get "as DTO" conversion working Signed-off-by: David Leangen --- .../org/apache/felix/converter/impl/ConvertingImpl.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java index 8b6f449c07e..2fa0edc3d2e 100644 --- a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java +++ b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java @@ -189,7 +189,7 @@ public Object to(Type type) { return convertToArray(); } else if (Collection.class.isAssignableFrom(targetAsClass)) { return convertToCollection(); - } else if (isDTOType(targetAsClass) || (DTO.class.equals(sourceClass) && DTO.class.isAssignableFrom(targetActualClass))) { + } else if (isDTOType(targetAsClass) || ((DTO.class.equals(sourceClass) || DTO.class.equals(targetAsClass)) && DTO.class.isAssignableFrom(targetActualClass))) { return convertToDTO(); } else if (isMapType(targetAsClass)) { return convertToMapType(); @@ -279,16 +279,19 @@ private T convertToCollection() { private T convertToDTO() { Map m = mapView(object, sourceClass, converter); + Class cls = targetAsClass; + if (DTO.class.equals(targetAsClass)) + cls = targetActualClass; try { T dto = (T) targetActualClass.newInstance(); for (Map.Entry entry : (Set) m.entrySet()) { Field f = null; try { - f = targetAsClass.getDeclaredField(mangleName(entry.getKey().toString())); + f = cls.getDeclaredField(mangleName(entry.getKey().toString())); } catch (NoSuchFieldException e) { try { - f = targetAsClass.getField(mangleName(entry.getKey().toString())); + f = cls.getField(mangleName(entry.getKey().toString())); } catch (NoSuchFieldException e1) { // There is not field with this name } From 17176ceb5e5783b2475e7b5f422946d57867f2af Mon Sep 17 00:00:00 2001 From: David Leangen Date: Tue, 3 Jan 2017 15:40:22 -0800 Subject: [PATCH 08/18] Additional updates to Schematizer Signed-off-by: David Leangen --- .../org/apache/felix/serializer/impl/json/JsonParser.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java index 23b7f0ef738..cb477a52be2 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java +++ b/converter/schematizer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java @@ -123,7 +123,10 @@ private static Object parseValue(String jsonValue) { case 'N': return null; default: - return Long.parseLong(jsonValue); + if (jsonValue.contains(".")) + return Double.parseDouble(jsonValue); + else + return Long.parseLong(jsonValue); } } From 5b233e762f0e52682f0f152531207d941d33327a Mon Sep 17 00:00:00 2001 From: David Leangen Date: Wed, 4 Jan 2017 14:23:28 -0800 Subject: [PATCH 09/18] Update API to include a Node's Field object. --- converter/schematizer/pom.xml | 4 +- .../org/apache/felix/schematizer/Node.java | 2 + .../felix/schematizer/impl/NodeImpl.java | 11 +++ .../schematizer/impl/SchematizerImpl.java | 41 ++++++-- .../impl/SchematizerServiceTest.java | 94 ++++++++++--------- 5 files changed, 99 insertions(+), 53 deletions(-) diff --git a/converter/schematizer/pom.xml b/converter/schematizer/pom.xml index 9f40d72686c..404947ea018 100644 --- a/converter/schematizer/pom.xml +++ b/converter/schematizer/pom.xml @@ -66,9 +66,9 @@ org.apache.felix.schematizer.impl.Activator - org.apache.felix.schematizer.*, org.apache.felix.serializer.* + org.apache.felix.schematizer.*, org.apache.felix.serializer.*, org.yaml.snakeyaml.* org.apache.felix.schematizer - org.apache.felix.converter, org.apache.felix.serializer, * + org.osgi.util.converter, org.osgi.service.serializer, * diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Node.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Node.java index ca74758b723..e9c9b940368 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Node.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Node.java @@ -19,6 +19,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; @@ -50,6 +51,7 @@ public static class DTO extends org.osgi.dto.DTO { */ String absolutePath(); Type type(); + Field field(); Optional> typeReference(); boolean isCollection(); Map children(); diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/NodeImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/NodeImpl.java index e05f65d1c17..f627210025f 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/NodeImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/NodeImpl.java @@ -15,6 +15,7 @@ */ package org.apache.felix.schematizer.impl; +import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; @@ -34,6 +35,7 @@ public class NodeImpl implements Node { private NodeImpl parent; private HashMap children = new HashMap<>(); + private Field field; public NodeImpl( String aName, @@ -107,6 +109,15 @@ public String absolutePath() { return absolutePath; } + @Override + public Field field() { + return field; + } + + public void field(Field aField) { + field = aField; + } + @SuppressWarnings( { "unchecked", "rawtypes" } ) @Override public Map children() { diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java index 7cf59d30310..930c92c34ea 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java @@ -66,9 +66,10 @@ public Optional from(String name, Map map) { // TODO: some validation of the Map here would be good SchemaImpl schema = new SchemaImpl(name); Object rootMap = map.get("/"); - Node.DTO rootDTO = new StandardConverter().convert( rootMap ).to( Node.DTO.class ); + Node.DTO rootDTO = new StandardConverter().convert(rootMap).to(Node.DTO.class); Map allNodes = new HashMap<>(); NodeImpl root = new NodeImpl(rootDTO, "", new Instantiator(classloaders), allNodes); + associateChildNodes(root); schema.add(root); schema.add(allNodes); return Optional.of(schema); @@ -184,7 +185,7 @@ private static SchemaImpl schematizeDTO( rootNode = new NodeImpl(contextPath, targetCls, false, contextPath + "/"); schema.add(rootNode); Map m = createMapFromDTO(name, targetCls, ref, contextPath, rules, schematizer); - m.values().stream().forEach(n -> n.parent(rootNode)); + processNodeParentsAndFields(rootNode, m); m.values().stream().filter(v -> v.absolutePath().equals(rootNode.absolutePath() + v.name())).forEach(v -> rootNode.add(v)); schema.add(m); return schema; @@ -201,10 +202,6 @@ private static SchemaImpl schematizeObject( SchemaImpl schema = new SchemaImpl(name); NodeImpl node = new NodeImpl(contextPath, targetCls, isCollection, contextPath + "/"); schema.add(node); -// Map m = createMapFromDTO(name, targetCls, ref, contextPath, rules, schematizer); -// m.values().stream().forEach(n -> n.parent(node)); -// m.values().stream().filter(v -> v.absolutePath().equals(node.absolutePath() + v.name())).forEach(v -> node.add(v)); -// schema.add(m); return schema; } @@ -407,4 +404,36 @@ public Type apply(String className) { return Object.class; } } + + static private void processNodeParentsAndFields(NodeImpl rootNode, Map map) { + for(NodeImpl n : map.values()) { + if (n.parent() != null) + continue; + n.parent(rootNode); + String fieldName = n.name(); + Class parentClass = rawClassOf(rootNode.type()); + try { + Field field = parentClass.getField(fieldName); + n.field(field); + } catch ( NoSuchFieldException e ) { + e.printStackTrace(); + } + } + } + + static private void associateChildNodes(NodeImpl rootNode) { + for (NodeImpl child: rootNode.childrenInternal().values()) { + child.parent(rootNode); + String fieldName = child.name(); + Class parentClass = rawClassOf(rootNode.type()); + try { + Field field = parentClass.getField(fieldName); + child.field(field); + } catch ( NoSuchFieldException e ) { + e.printStackTrace(); + } + + associateChildNodes(child); + } + } } diff --git a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java index 7d2cabee163..51f0b8a4654 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java +++ b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java @@ -59,40 +59,40 @@ public void testSchematizeDTO() { Schema s = opt.get(); assertNotNull(s); Node root = s.rootNode(); - assertNodeEquals("", "/", false, MyDTO.class, root); + assertNodeEquals("", "/", false, MyDTO.class, false, root); assertEquals(4, root.children().size()); Node pingNode = root.children().get("/ping"); - assertNodeEquals("ping", "/ping", false, String.class, pingNode); + assertNodeEquals("ping", "/ping", false, String.class, true, pingNode); Node pongNode = root.children().get("/pong"); - assertNodeEquals("pong", "/pong", false, Long.class, pongNode); + assertNodeEquals("pong", "/pong", false, Long.class, true, pongNode); Node countNode = root.children().get("/count"); - assertNodeEquals("count", "/count", false, MyDTO.Count.class, countNode); + assertNodeEquals("count", "/count", false, MyDTO.Count.class, true, countNode); Node embeddedNode = root.children().get("/embedded"); assertEquals(3, embeddedNode.children().size()); - assertNodeEquals("embedded", "/embedded", false, MyEmbeddedDTO.class, embeddedNode); + assertNodeEquals("embedded", "/embedded", false, MyEmbeddedDTO.class, true, embeddedNode); Node marcoNode = embeddedNode.children().get("/marco"); - assertNodeEquals("marco", "/embedded/marco", false, String.class, marcoNode); + assertNodeEquals("marco", "/embedded/marco", false, String.class, true, marcoNode); Node poloNode = embeddedNode.children().get("/polo"); - assertNodeEquals("polo", "/embedded/polo", false, Long.class, poloNode); + assertNodeEquals("polo", "/embedded/polo", false, Long.class, true, poloNode); Node alphaNode = embeddedNode.children().get("/alpha"); - assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, alphaNode); + assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, true, alphaNode); Node sRoot = s.nodeAtPath("/").get(); - assertNodeEquals("", "/", false, MyDTO.class, sRoot); + assertNodeEquals("", "/", false, MyDTO.class, false, sRoot); Node sPingNode = s.nodeAtPath("/ping").get(); - assertNodeEquals("ping", "/ping", false, String.class, sPingNode); + assertNodeEquals("ping", "/ping", false, String.class, true, sPingNode); Node sPongNode = s.nodeAtPath("/pong").get(); - assertNodeEquals("pong", "/pong", false, Long.class, sPongNode); + assertNodeEquals("pong", "/pong", false, Long.class, true, sPongNode); Node sCountNode = s.nodeAtPath("/count").get(); - assertNodeEquals("count", "/count", false, MyDTO.Count.class, sCountNode); + assertNodeEquals("count", "/count", false, MyDTO.Count.class, true, sCountNode); Node sEmbeddedNode = s.nodeAtPath("/embedded").get(); - assertNodeEquals("embedded", "/embedded", false, MyEmbeddedDTO.class, sEmbeddedNode); + assertNodeEquals("embedded", "/embedded", false, MyEmbeddedDTO.class, true, sEmbeddedNode); Node sMarcoNode = s.nodeAtPath("/embedded/marco").get(); - assertNodeEquals("marco", "/embedded/marco", false, String.class, sMarcoNode); + assertNodeEquals("marco", "/embedded/marco", false, String.class, true, sMarcoNode); Node sPoloNode = s.nodeAtPath("/embedded/polo").get(); - assertNodeEquals("polo", "/embedded/polo", false, Long.class, sPoloNode); + assertNodeEquals("polo", "/embedded/polo", false, Long.class, true, sPoloNode); Node sAlphaNode = s.nodeAtPath("/embedded/alpha").get(); - assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, sAlphaNode); + assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, true, sAlphaNode); } @Test @@ -107,32 +107,32 @@ public void testSchematizeDTOWithColletion() { Schema s = opt.get(); assertNotNull(s); Node root = s.rootNode(); - assertNodeEquals("", "/", false, new TypeReference>>(){}.getType(), root); + assertNodeEquals("", "/", false, new TypeReference>>(){}.getType(), false, root); assertEquals(4, root.children().size()); Node pingNode = root.children().get("/ping"); - assertNodeEquals("ping", "/ping", false, String.class, pingNode); + assertNodeEquals("ping", "/ping", false, String.class, true, pingNode); Node pongNode = root.children().get("/pong"); - assertNodeEquals("pong", "/pong", false, Long.class, pongNode); + assertNodeEquals("pong", "/pong", false, Long.class, true, pongNode); Node countNode = root.children().get("/count"); - assertNodeEquals("count", "/count", false, MyDTO3.Count.class, countNode); + assertNodeEquals("count", "/count", false, MyDTO3.Count.class, true, countNode); Node embeddedNode = root.children().get("/embedded"); assertEquals(1, embeddedNode.children().size()); - assertNodeEquals("embedded", "/embedded", true, new TypeReference>(){}.getType(), embeddedNode); + assertNodeEquals("embedded", "/embedded", true, new TypeReference>(){}.getType(), true, embeddedNode); Node valueNode = embeddedNode.children().get("/value"); - assertNodeEquals("value", "/embedded/value", false, String.class, valueNode); + assertNodeEquals("value", "/embedded/value", false, String.class, true, valueNode); Node sRoot = s.nodeAtPath("/").get(); - assertNodeEquals("", "/", false, new TypeReference>>(){}.getType(), sRoot); + assertNodeEquals("", "/", false, new TypeReference>>(){}.getType(), false, sRoot); Node sPingNode = s.nodeAtPath("/ping").get(); - assertNodeEquals("ping", "/ping", false, String.class, sPingNode); + assertNodeEquals("ping", "/ping", false, String.class, true, sPingNode); Node sPongNode = s.nodeAtPath("/pong").get(); - assertNodeEquals("pong", "/pong", false, Long.class, sPongNode); + assertNodeEquals("pong", "/pong", false, Long.class, true, sPongNode); Node sCountNode = s.nodeAtPath("/count").get(); - assertNodeEquals("count", "/count", false, MyDTO3.Count.class, sCountNode); + assertNodeEquals("count", "/count", false, MyDTO3.Count.class, true, sCountNode); Node sEmbeddedNode = s.nodeAtPath("/embedded").get(); - assertNodeEquals("embedded", "/embedded", true, new TypeReference>(){}.getType(), sEmbeddedNode); + assertNodeEquals("embedded", "/embedded", true, new TypeReference>(){}.getType(), true, sEmbeddedNode); Node sValueNode = s.nodeAtPath("/embedded/value").get(); - assertNodeEquals("value", "/embedded/value", false, String.class, sValueNode); + assertNodeEquals("value", "/embedded/value", false, String.class, true, sValueNode); } @Test @@ -193,39 +193,39 @@ private void testSchema(Schema s) { assertNotNull(s); Node root = s.rootNode(); assertEquals(4, root.children().size()); - assertNodeEquals("", "/", false, MyDTO.class, root); + assertNodeEquals("", "/", false, MyDTO.class, false, root); Node pingNode = root.children().get("/ping"); - assertNodeEquals("ping", "/ping", false, String.class, pingNode); + assertNodeEquals("ping", "/ping", false, String.class, true, pingNode); Node pongNode = root.children().get("/pong"); - assertNodeEquals("pong", "/pong", false, Long.class, pongNode); + assertNodeEquals("pong", "/pong", false, Long.class, true, pongNode); Node countNode = root.children().get("/count"); - assertNodeEquals("count", "/count", false, MyDTO.Count.class, countNode); + assertNodeEquals("count", "/count", false, MyDTO.Count.class, true, countNode); Node embeddedNode = root.children().get("/embedded"); assertEquals(3, embeddedNode.children().size()); - assertNodeEquals("embedded", "/embedded", false, MyEmbeddedDTO.class, embeddedNode); + assertNodeEquals("embedded", "/embedded", false, MyEmbeddedDTO.class, true, embeddedNode); Node marcoNode = embeddedNode.children().get("/marco"); - assertNodeEquals("marco", "/embedded/marco", false, String.class, marcoNode); + assertNodeEquals("marco", "/embedded/marco", false, String.class, true, marcoNode); Node poloNode = embeddedNode.children().get("/polo"); - assertNodeEquals("polo", "/embedded/polo", false, Long.class, poloNode); + assertNodeEquals("polo", "/embedded/polo", false, Long.class, true, poloNode); Node alphaNode = embeddedNode.children().get("/alpha"); - assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, alphaNode); + assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, true, alphaNode); Node sRoot = s.nodeAtPath("/").get(); - assertNodeEquals("", "/", false, MyDTO.class, sRoot); + assertNodeEquals("", "/", false, MyDTO.class, false, sRoot); Node sPingNode = s.nodeAtPath("/ping").get(); - assertNodeEquals("ping", "/ping", false, String.class, sPingNode); + assertNodeEquals("ping", "/ping", false, String.class, true, sPingNode); Node sPongNode = s.nodeAtPath("/pong").get(); - assertNodeEquals("pong", "/pong", false, Long.class, sPongNode); + assertNodeEquals("pong", "/pong", false, Long.class, true, sPongNode); Node sCountNode = s.nodeAtPath("/count").get(); - assertNodeEquals("count", "/count", false, MyDTO.Count.class, sCountNode); + assertNodeEquals("count", "/count", false, MyDTO.Count.class, true, sCountNode); Node sEmbeddedNode = s.nodeAtPath("/embedded").get(); - assertNodeEquals("embedded", "/embedded", false, MyEmbeddedDTO.class, sEmbeddedNode); + assertNodeEquals("embedded", "/embedded", false, MyEmbeddedDTO.class, true, sEmbeddedNode); Node sMarcoNode = s.nodeAtPath("/embedded/marco").get(); - assertNodeEquals("marco", "/embedded/marco", false, String.class, sMarcoNode); + assertNodeEquals("marco", "/embedded/marco", false, String.class, true, sMarcoNode); Node sPoloNode = s.nodeAtPath("/embedded/polo").get(); - assertNodeEquals("polo", "/embedded/polo", false, Long.class, sPoloNode); + assertNodeEquals("polo", "/embedded/polo", false, Long.class, true, sPoloNode); Node sAlphaNode = s.nodeAtPath("/embedded/alpha").get(); - assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, sAlphaNode); + assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, true, sAlphaNode); } @Test @@ -243,12 +243,16 @@ public void testVisitor() { assertEquals("::::count::embedded::alpha::marco::polo::ping::pong", sb.toString()); } - private void assertNodeEquals(String name, String path, boolean isCollection, Object type, Node node) { + private void assertNodeEquals(String name, String path, boolean isCollection, Object type, boolean fieldNotNull, Node node) { assertNotNull(node); assertEquals(name, node.name()); assertEquals(path, node.absolutePath()); assertEquals(isCollection, node.isCollection()); assertEquals(type, node.type()); + if (fieldNotNull) + assertNotNull(node.field()); + else + assertTrue(node.field() == null); } private void assertNodeDTOEquals(String name, String path, boolean isCollection, Class type, Node.DTO node) { From 9f879a5fc88e6ba8cacd66d1f2205ae59d8465df Mon Sep 17 00:00:00 2001 From: David Leangen Date: Tue, 3 Jan 2017 19:01:16 -0800 Subject: [PATCH 10/18] Merged a few additional fixes with this branch --- .../felix/schematizer/StandardSchematizer.java | 3 ++- .../felix/serializer/impl/json/JsonParser.java | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/StandardSchematizer.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/StandardSchematizer.java index 8a0514c981d..48bfd4d9b08 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/StandardSchematizer.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/StandardSchematizer.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Optional; +import org.apache.felix.schematizer.impl.SchematizerImpl; import org.osgi.dto.DTO; import org.osgi.util.converter.TypeReference; @@ -26,7 +27,7 @@ public class StandardSchematizer implements Schematizer { private final Schematizer schematizer; public StandardSchematizer() { - schematizer = null;//new SchematizerImpl(); + schematizer = new SchematizerImpl(); } @Override diff --git a/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java b/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java index ae2b4a90b33..5c8adfd4887 100644 --- a/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java +++ b/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/JsonParser.java @@ -89,12 +89,24 @@ public Map getParsed() { } private static Pair parseKeyValue(String jsonKeyValue) { + // Should be able to accept an empty key/value + if (jsonKeyValue.isEmpty()) + return null; + 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))); + try + { + return new Pair<>(matcher.group(1), parseValue(matcher.group(2))); + } + catch ( Exception e ) { + // The matcher throws as exeption when there is an empty value, when it should just return null; + // TODO: find a better solution to solve this problem. + return null; + } } private static Object parseValue(String jsonValue) { @@ -142,7 +154,8 @@ private static Map parseObject(String jsonObject) { for (String element : parseKeyValueListRaw(jsonObject)) { Pair pair = parseKeyValue(element); - values.put(pair.key, pair.value); + if (pair != null) + values.put(pair.key, pair.value); } return values; From 67a39be7f44d64738982166d893438e3c86feee4 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Thu, 5 Jan 2017 04:17:35 -0800 Subject: [PATCH 11/18] Added convention-based means of signalling the type of a Collection in a DTO --- .../schematizer/impl/SchematizerImpl.java | 37 ++++++++++++++- .../schematizer/impl/CollectionType.java | 12 +++++ .../apache/felix/schematizer/impl/MyDTO3.java | 47 ------------------- .../apache/felix/schematizer/impl/MyDTO4.java | 35 ++++++++++++++ .../impl/SchematizerServiceTest.java | 46 ++++++++++++++++++ 5 files changed, 129 insertions(+), 48 deletions(-) create mode 100644 converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/CollectionType.java create mode 100644 converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/MyDTO4.java diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java index 930c92c34ea..8e3a710c12d 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java @@ -16,11 +16,14 @@ */ package org.apache.felix.schematizer.impl; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -282,8 +285,10 @@ else if (fieldRef != null ) Class collectionType; if (collectionTypeAnnotation != null) collectionType = collectionTypeAnnotation.value(); + else if (hasCollectionTypeAnnotation(field)) + collectionType = collectionTypeOf(field); else - collectionType = Object.class; + collectionType = Object.class; node = new CollectionNode( field.getName(), collectionType, @@ -436,4 +441,34 @@ static private void associateChildNodes(NodeImpl rootNode) { associateChildNodes(child); } } + + static private boolean hasCollectionTypeAnnotation(Field field) { + if (field == null) + return false; + + Annotation[] annotations = field.getAnnotations(); + if (annotations.length == 0) + return false; + + return Arrays.stream(annotations) + .map(a -> a.annotationType().getName()) + .anyMatch(a -> "CollectionType".equals(a.substring(a.lastIndexOf(".") + 1) )); + } + + static private Class collectionTypeOf(Field field) { + Annotation[] annotations = field.getAnnotations(); + + Annotation annotation = Arrays.stream(annotations) + .filter(a -> "CollectionType".equals(a.annotationType().getName().substring(a.annotationType().getName().lastIndexOf(".") + 1) )) + .findFirst() + .get(); + + try { + Method m = annotation.annotationType().getMethod("value"); + Class value = (Class)m.invoke(annotation, null); + return value; + } catch ( Exception e ) { + return null; + } + } } diff --git a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/CollectionType.java b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/CollectionType.java new file mode 100644 index 00000000000..afd64913b66 --- /dev/null +++ b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/CollectionType.java @@ -0,0 +1,12 @@ +package org.apache.felix.schematizer.impl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface CollectionType { + Class value() default Object.class; +} diff --git a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/MyDTO3.java b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/MyDTO3.java index 4c8be444695..42d653780c3 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/MyDTO3.java +++ b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/MyDTO3.java @@ -1,51 +1,4 @@ /* - @Test - public void testSchematizeDTO() { - Optional opt = schematizer - .rule("MyDTO", new TypeReference(){}) - .rule("MyDTO", "/embedded", new TypeReference(){}) - .get("MyDTO"); - - assertTrue(opt.isPresent()); - Schema s = opt.get(); - assertNotNull(s); - Node root = s.rootNode(); - assertNodeEquals("", "/", false, MyDTO.class, root); - assertEquals(4, root.children().size()); - Node pingNode = root.children().get("/ping"); - assertNodeEquals("ping", "/ping", false, String.class, pingNode); - Node pongNode = root.children().get("/pong"); - assertNodeEquals("pong", "/pong", false, Long.class, pongNode); - Node countNode = root.children().get("/count"); - assertNodeEquals("count", "/count", false, MyDTO.Count.class, countNode); - Node embeddedNode = root.children().get("/embedded"); - assertEquals(3, embeddedNode.children().size()); - assertNodeEquals("embedded", "/embedded", false, MyEmbeddedDTO.class, embeddedNode); - Node marcoNode = embeddedNode.children().get("/marco"); - assertNodeEquals("marco", "/embedded/marco", false, String.class, marcoNode); - Node poloNode = embeddedNode.children().get("/polo"); - assertNodeEquals("polo", "/embedded/polo", false, Long.class, poloNode); - Node alphaNode = embeddedNode.children().get("/alpha"); - assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, alphaNode); - - Node sRoot = s.nodeAtPath("/").get(); - assertNodeEquals("", "/", false, MyDTO.class, sRoot); - Node sPingNode = s.nodeAtPath("/ping").get(); - assertNodeEquals("ping", "/ping", false, String.class, sPingNode); - Node sPongNode = s.nodeAtPath("/pong").get(); - assertNodeEquals("pong", "/pong", false, Long.class, sPongNode); - Node sCountNode = s.nodeAtPath("/count").get(); - assertNodeEquals("count", "/count", false, MyDTO.Count.class, sCountNode); - Node sEmbeddedNode = s.nodeAtPath("/embedded").get(); - assertNodeEquals("embedded", "/embedded", false, MyEmbeddedDTO.class, sEmbeddedNode); - Node sMarcoNode = s.nodeAtPath("/embedded/marco").get(); - assertNodeEquals("marco", "/embedded/marco", false, String.class, sMarcoNode); - Node sPoloNode = s.nodeAtPath("/embedded/polo").get(); - assertNodeEquals("polo", "/embedded/polo", false, Long.class, sPoloNode); - Node sAlphaNode = s.nodeAtPath("/embedded/alpha").get(); - assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, sAlphaNode); - } - * 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. diff --git a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/MyDTO4.java b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/MyDTO4.java new file mode 100644 index 00000000000..69ee8519e9f --- /dev/null +++ b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/MyDTO4.java @@ -0,0 +1,35 @@ +/* + * 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.schematizer.impl; + +import java.util.List; + +import org.osgi.dto.DTO; + +public class MyDTO4 extends DTO { + public enum Count { ONE, TWO, THREE } + + public Count count; + + public String ping; + + public long pong; + + @org.apache.felix.schematizer.impl.CollectionType(MyEmbeddedDTO.class) + public List embedded; +} + diff --git a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java index 51f0b8a4654..e4f18a90ef7 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java +++ b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java @@ -135,6 +135,52 @@ public void testSchematizeDTOWithColletion() { assertNodeEquals("value", "/embedded/value", false, String.class, true, sValueNode); } + @Test + public void testSchematizeDTOWithAnnotatedColletion() { + Optional opt = schematizer + .rule("MyDTO4", new TypeReference(){}) + .get("MyDTO4"); + + assertTrue(opt.isPresent()); + Schema s = opt.get(); + assertNotNull(s); + Node root = s.rootNode(); + assertNodeEquals("", "/", false, MyDTO4.class, false, root); + assertEquals(4, root.children().size()); + Node pingNode = root.children().get("/ping"); + assertNodeEquals("ping", "/ping", false, String.class, true, pingNode); + Node pongNode = root.children().get("/pong"); + assertNodeEquals("pong", "/pong", false, Long.class, true, pongNode); + Node countNode = root.children().get("/count"); + assertNodeEquals("count", "/count", false, MyDTO4.Count.class, true, countNode); + Node embeddedNode = root.children().get("/embedded"); + assertEquals(3, embeddedNode.children().size()); + assertNodeEquals("embedded", "/embedded", true, MyEmbeddedDTO.class, true, embeddedNode); + Node marcoNode = embeddedNode.children().get("/marco"); + assertNodeEquals("marco", "/embedded/marco", false, String.class, true, marcoNode); + Node poloNode = embeddedNode.children().get("/polo"); + assertNodeEquals("polo", "/embedded/polo", false, Long.class, true, poloNode); + Node alphaNode = embeddedNode.children().get("/alpha"); + assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, true, alphaNode); + + Node sRoot = s.nodeAtPath("/").get(); + assertNodeEquals("", "/", false, MyDTO4.class, false, sRoot); + Node sPingNode = s.nodeAtPath("/ping").get(); + assertNodeEquals("ping", "/ping", false, String.class, true, sPingNode); + Node sPongNode = s.nodeAtPath("/pong").get(); + assertNodeEquals("pong", "/pong", false, Long.class, true, sPongNode); + Node sCountNode = s.nodeAtPath("/count").get(); + assertNodeEquals("count", "/count", false, MyDTO4.Count.class, true, sCountNode); + Node sEmbeddedNode = s.nodeAtPath("/embedded").get(); + assertNodeEquals("embedded", "/embedded", true, MyEmbeddedDTO.class, true, sEmbeddedNode); + Node sMarcoNode = s.nodeAtPath("/embedded/marco").get(); + assertNodeEquals("marco", "/embedded/marco", false, String.class, true, sMarcoNode); + Node sPoloNode = s.nodeAtPath("/embedded/polo").get(); + assertNodeEquals("polo", "/embedded/polo", false, Long.class, true, sPoloNode); + Node sAlphaNode = s.nodeAtPath("/embedded/alpha").get(); + assertNodeEquals("alpha", "/embedded/alpha", false, MyEmbeddedDTO.Alpha.class, true, sAlphaNode); + } + @Test public void testSchematizeToMap() { Optional opt = schematizer From 88af56982bd3d904a23d1bf06b961e3ed0eb2955 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Thu, 5 Jan 2017 11:16:30 -0800 Subject: [PATCH 12/18] Added valuesAt --- .../org/apache/felix/schematizer/Schema.java | 3 + .../felix/schematizer/impl/SchemaImpl.java | 54 ++++++++- .../schematizer/impl/CollectionType.java | 16 +++ .../felix/schematizer/impl/SchemaTest.java | 105 ++++++++++++++++++ 4 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schema.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schema.java index d9ec6b91cfd..b46d7f0c9fc 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schema.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schema.java @@ -15,6 +15,7 @@ */ package org.apache.felix.schematizer; +import java.util.Collection; import java.util.Map; import java.util.Optional; @@ -28,4 +29,6 @@ public interface Schema { * Recursively visits all nodes in the {@code Schema} for processing. */ void visit(NodeVisitor visitor); + + Collection valuesAt(String path, Object object); } diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java index 0829561933a..c7be45583e6 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java @@ -15,14 +15,21 @@ */ package org.apache.felix.schematizer.impl; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import org.apache.felix.schematizer.Node; import org.apache.felix.schematizer.NodeVisitor; import org.apache.felix.schematizer.Schema; +import org.osgi.util.converter.Converter; +import org.osgi.util.converter.StandardConverter; public class SchemaImpl implements Schema @@ -79,4 +86,49 @@ Map toMapInternal() { public void visit(NodeVisitor visitor) { nodes.values().stream().forEach(n -> visitor.apply(n)); } -} + + @Override + public Collection valuesAt(String path, Object object) { + final Converter converter = new StandardConverter(); + @SuppressWarnings( "unchecked" ) + final Map map = (Map)converter.convert( object ).to( Map.class ); + if (map == null || map.isEmpty()) + return Collections.emptyList(); + + if (path.startsWith("/")) + path = path.substring(1); + String[] pathParts = path.split("/"); + if (pathParts.length <= 0) + return Collections.emptyList(); + + List contexts = Arrays.asList(pathParts); + List values = new ArrayList<>(); + + return valuesAt("", map, contexts, 0, values); + } + + private Collection valuesAt(String context, Map objectMap, List contexts, int currentIndex, List values) { + List result = new ArrayList<>(); + String currentContext = context + contexts.get(currentIndex); + Object o = objectMap.get(currentContext); + if (o instanceof Map) + toString(); + if (o instanceof List) { + @SuppressWarnings( "unchecked" ) + List l = (List)o; + if (currentIndex == contexts.size() - 1) { + // We are at the end, so just add the collection + result.add(l); + return result; + } + + currentContext = "/" + contexts.get(++currentIndex); + for (Object o2 : l) + result.addAll(valuesAt(currentContext, o2)); + } else { + result.add( o ); + } + + return result; + } +} \ No newline at end of file diff --git a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/CollectionType.java b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/CollectionType.java index afd64913b66..f1e12db7f65 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/CollectionType.java +++ b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/CollectionType.java @@ -1,3 +1,19 @@ +/* + * 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.schematizer.impl; import java.lang.annotation.ElementType; diff --git a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java new file mode 100644 index 00000000000..ca458508d5d --- /dev/null +++ b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java @@ -0,0 +1,105 @@ +/* + * 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.schematizer.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.apache.felix.schematizer.Schema; +import org.apache.felix.schematizer.impl.MyDTO3.Count; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.osgi.util.converter.TypeReference; + +import junit.framework.AssertionFailedError; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class SchemaTest { + private SchematizerImpl schematizer; + + @Before + public void setUp() { + schematizer = new SchematizerImpl(); + } + + @After + public void tearDown() { + schematizer = null; + } + + @Test + public void testValues() { + Optional opt = schematizer + .rule("MyDTO", new TypeReference>>(){}) + .rule("MyDTO", "/embedded", new TypeReference>(){}) + .rule("MyDTO", "/embedded/value", String.class) + .get("MyDTO"); + + assertTrue(opt.isPresent()); + Schema s = opt.get(); + assertNotNull(s); + + MyEmbeddedDTO2 embedded1 = new MyEmbeddedDTO2<>(); + embedded1.value = "value1"; + MyEmbeddedDTO2 embedded2 = new MyEmbeddedDTO2<>(); + embedded2.value = "value2"; + MyEmbeddedDTO2 embedded3 = new MyEmbeddedDTO2<>(); + embedded3.value = "value3"; + + MyDTO3> dto = new MyDTO3<>(); + dto.ping = "lalala"; + dto.pong = Long.MIN_VALUE; + dto.count = Count.ONE; + dto.embedded = new ArrayList<>(); + dto.embedded.add(embedded1); + dto.embedded.add(embedded2); + dto.embedded.add(embedded3); + + assertEquals("lalala", s.valuesAt("/ping", dto).iterator().next()); + assertEquals(Long.MIN_VALUE, s.valuesAt("/pong", dto).iterator().next()); + assertEquals(Count.ONE, s.valuesAt("/count", dto).iterator().next()); + assertNotNull(s.valuesAt("/embedded", dto)); + assertListEquals(Arrays.asList(new String[]{"value1", "value2", "value3"}), s.valuesAt("/embedded/value", dto)); + } + + @SuppressWarnings( { "rawtypes", "unchecked" } ) + private boolean assertListEquals(List expected, Collection actual) { + if (expected == null || actual == null) + throw new AssertionFailedError("The collection is null"); + + if (expected.size() != actual.size()) + throw new AssertionFailedError("Expected list size of " + expected.size() + ", but was: " + actual.size()); + + List actualList = new ArrayList<>(); + if (actual instanceof List) + actualList = (List)actual; + else + actualList.addAll(actual); + + for (int i = 0; i < actual.size(); i++) + assertEquals(expected.get(i), actualList.get(i)); + + return true; + } +} From 495f500e4e4d617f4a372b8745085a1308225a30 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Thu, 5 Jan 2017 20:47:16 -0800 Subject: [PATCH 13/18] Some minor cleanup and an additional test case --- .../felix/schematizer/impl/SchemaImpl.java | 2 -- .../felix/schematizer/impl/SchemaTest.java | 32 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java index c7be45583e6..acbad776c11 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java @@ -111,8 +111,6 @@ private Collection valuesAt(String context, Map objectMap, Li List result = new ArrayList<>(); String currentContext = context + contexts.get(currentIndex); Object o = objectMap.get(currentContext); - if (o instanceof Map) - toString(); if (o instanceof List) { @SuppressWarnings( "unchecked" ) List l = (List)o; diff --git a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java index ca458508d5d..1048f950b40 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java +++ b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java @@ -83,6 +83,38 @@ public void testValues() { assertListEquals(Arrays.asList(new String[]{"value1", "value2", "value3"}), s.valuesAt("/embedded/value", dto)); } + @Test + public void testNullValues() { + Optional opt = schematizer + .rule("MyDTO", new TypeReference>>(){}) + .rule("MyDTO", "/embedded", new TypeReference>(){}) + .rule("MyDTO", "/embedded/value", String.class) + .get("MyDTO"); + + assertTrue(opt.isPresent()); + Schema s = opt.get(); + assertNotNull(s); + + MyEmbeddedDTO2 embedded1 = new MyEmbeddedDTO2<>(); + MyEmbeddedDTO2 embedded2 = new MyEmbeddedDTO2<>(); + MyEmbeddedDTO2 embedded3 = new MyEmbeddedDTO2<>(); + + MyDTO3> dto = new MyDTO3<>(); + dto.ping = "lalala"; + dto.pong = Long.MIN_VALUE; + dto.count = Count.ONE; + dto.embedded = new ArrayList<>(); + dto.embedded.add(embedded1); + dto.embedded.add(embedded2); + dto.embedded.add(embedded3); + + assertEquals("lalala", s.valuesAt("/ping", dto).iterator().next()); + assertEquals(Long.MIN_VALUE, s.valuesAt("/pong", dto).iterator().next()); + assertEquals(Count.ONE, s.valuesAt("/count", dto).iterator().next()); + assertNotNull(s.valuesAt("/embedded", dto)); + assertListEquals(Arrays.asList(new String[]{null, null, null}), s.valuesAt("/embedded/value", dto)); + } + @SuppressWarnings( { "rawtypes", "unchecked" } ) private boolean assertListEquals(List expected, Collection actual) { if (expected == null || actual == null) From db17531e21b0b0368eb94d92870665d68f054b70 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Mon, 9 Jan 2017 12:02:14 -0800 Subject: [PATCH 14/18] Updated to process embedded values --- .../felix/schematizer/impl/SchemaImpl.java | 41 +++++++++++--- .../felix/schematizer/impl/SchemaTest.java | 53 ++++++++++++++++--- 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java index acbad776c11..ad1df5cd648 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java @@ -24,10 +24,13 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import org.apache.felix.schematizer.Node; +import org.apache.felix.schematizer.Node.DTO; import org.apache.felix.schematizer.NodeVisitor; import org.apache.felix.schematizer.Schema; +import org.apache.felix.schematizer.SchematizingConverter; import org.osgi.util.converter.Converter; import org.osgi.util.converter.StandardConverter; @@ -102,31 +105,57 @@ public Collection valuesAt(String path, Object object) { return Collections.emptyList(); List contexts = Arrays.asList(pathParts); - List values = new ArrayList<>(); - return valuesAt("", map, contexts, 0, values); + return valuesAt("", map, contexts, 0); } - private Collection valuesAt(String context, Map objectMap, List contexts, int currentIndex, List values) { + private Collection valuesAt(String context, Map objectMap, List contexts, int currentIndex) { List result = new ArrayList<>(); - String currentContext = context + contexts.get(currentIndex); + String currentContext = contexts.get(currentIndex); Object o = objectMap.get(currentContext); if (o instanceof List) { @SuppressWarnings( "unchecked" ) List l = (List)o; if (currentIndex == contexts.size() - 1) { // We are at the end, so just add the collection - result.add(l); + result.add(convertToType("/" + currentContext, l)); return result; } currentContext = "/" + contexts.get(++currentIndex); for (Object o2 : l) result.addAll(valuesAt(currentContext, o2)); + } else if (o instanceof Map){ + if (currentIndex == contexts.size() - 1) { + // We are at the end, so just add the result + result.add(convertToType("/" + currentContext, (Map)o)); + return result; + } + + result.addAll( valuesAt( currentContext, (Map)o, contexts, ++currentIndex ) ); } else { - result.add( o ); + result.add(o); } return result; } + + private Object convertToType( String path, Map map ) { + Optional node = nodeAtPath(path); + if (!node.isPresent()) + return map; + + Object result = new StandardConverter().convert(map).sourceAs(DTO.class).to(node.get().type()); + return result; + } + + private List convertToType( String path, List list ) { + Optional node = nodeAtPath(path); + if (!node.isPresent()) + return list; + + return list.stream() + .map( v -> new StandardConverter().convert(v).sourceAs(DTO.class).to(node.get().type())) + .collect( Collectors.toList() ); + } } \ No newline at end of file diff --git a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java index 1048f950b40..be7b4cd74f7 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java +++ b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchemaTest.java @@ -23,7 +23,7 @@ import java.util.Optional; import org.apache.felix.schematizer.Schema; -import org.apache.felix.schematizer.impl.MyDTO3.Count; +import org.apache.felix.schematizer.impl.MyEmbeddedDTO.Alpha; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -31,9 +31,7 @@ import junit.framework.AssertionFailedError; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class SchemaTest { private SchematizerImpl schematizer; @@ -70,7 +68,7 @@ public void testValues() { MyDTO3> dto = new MyDTO3<>(); dto.ping = "lalala"; dto.pong = Long.MIN_VALUE; - dto.count = Count.ONE; + dto.count = MyDTO3.Count.ONE; dto.embedded = new ArrayList<>(); dto.embedded.add(embedded1); dto.embedded.add(embedded2); @@ -78,11 +76,50 @@ public void testValues() { assertEquals("lalala", s.valuesAt("/ping", dto).iterator().next()); assertEquals(Long.MIN_VALUE, s.valuesAt("/pong", dto).iterator().next()); - assertEquals(Count.ONE, s.valuesAt("/count", dto).iterator().next()); + assertEquals(MyDTO3.Count.ONE, s.valuesAt("/count", dto).iterator().next()); assertNotNull(s.valuesAt("/embedded", dto)); + Object embeddedList = s.valuesAt("/embedded", dto).iterator().next(); + assertNotNull(embeddedList); + assertTrue(embeddedList instanceof List); + assertFalse(((List)embeddedList).isEmpty()); + Object embeddedObject = ((List)embeddedList).get(0); + assertTrue(embeddedObject instanceof MyEmbeddedDTO2); assertListEquals(Arrays.asList(new String[]{"value1", "value2", "value3"}), s.valuesAt("/embedded/value", dto)); } + @Test + public void testEmbeddedValues() { + Optional opt = schematizer + .rule("MyDTO", new TypeReference(){}) + .rule("MyDTO", "/embedded", new TypeReference(){}) + .get("MyDTO"); + + assertTrue(opt.isPresent()); + Schema s = opt.get(); + assertNotNull(s); + + MyEmbeddedDTO embedded = new MyEmbeddedDTO(); + embedded.alpha = Alpha.A; + embedded.marco = "mmmm"; + embedded.polo = 66; + + MyDTO dto = new MyDTO(); + dto.ping = "lalala"; + dto.pong = Long.MIN_VALUE; + dto.count = MyDTO.Count.ONE; + dto.embedded = embedded; + + assertEquals("lalala", s.valuesAt("/ping", dto).iterator().next()); + assertEquals(Long.MIN_VALUE, s.valuesAt("/pong", dto).iterator().next()); + assertEquals(MyDTO.Count.ONE, s.valuesAt("/count", dto).iterator().next()); + assertNotNull(s.valuesAt("/embedded", dto)); + Object embeddedObject = s.valuesAt("/embedded", dto).iterator().next(); + assertTrue(embeddedObject instanceof MyEmbeddedDTO); + assertEquals(Alpha.A, s.valuesAt("/embedded/alpha", dto).iterator().next()); + assertEquals("mmmm", s.valuesAt("/embedded/marco", dto).iterator().next()); + assertEquals(66L, s.valuesAt("/embedded/polo", dto).iterator().next()); + } + @Test public void testNullValues() { Optional opt = schematizer @@ -102,7 +139,7 @@ public void testNullValues() { MyDTO3> dto = new MyDTO3<>(); dto.ping = "lalala"; dto.pong = Long.MIN_VALUE; - dto.count = Count.ONE; + dto.count = MyDTO3.Count.ONE; dto.embedded = new ArrayList<>(); dto.embedded.add(embedded1); dto.embedded.add(embedded2); @@ -110,7 +147,7 @@ public void testNullValues() { assertEquals("lalala", s.valuesAt("/ping", dto).iterator().next()); assertEquals(Long.MIN_VALUE, s.valuesAt("/pong", dto).iterator().next()); - assertEquals(Count.ONE, s.valuesAt("/count", dto).iterator().next()); + assertEquals(MyDTO3.Count.ONE, s.valuesAt("/count", dto).iterator().next()); assertNotNull(s.valuesAt("/embedded", dto)); assertListEquals(Arrays.asList(new String[]{null, null, null}), s.valuesAt("/embedded/value", dto)); } From 84ec3b1c9978d635c4614f8c609a6151486806a6 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Mon, 9 Jan 2017 18:15:51 -0800 Subject: [PATCH 15/18] A addition to the API, and a few implementation updates to the way parent nodes are associated to child nodes --- .../org/apache/felix/schematizer/Schema.java | 1 + .../felix/schematizer/impl/SchemaImpl.java | 20 ++++++++-- .../schematizer/impl/SchematizerImpl.java | 18 +-------- .../impl/SchematizerServiceTest.java | 39 +++++++++++++++++++ 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schema.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schema.java index b46d7f0c9fc..94d61d5c822 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schema.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/Schema.java @@ -23,6 +23,7 @@ public interface Schema { String name(); Node rootNode(); Optional nodeAtPath(String absolutePath); + Optional parentOf(Node aNode); Map toMap(); /** diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java index ad1df5cd648..c0dd57c6ae5 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java @@ -30,7 +30,6 @@ import org.apache.felix.schematizer.Node.DTO; import org.apache.felix.schematizer.NodeVisitor; import org.apache.felix.schematizer.Schema; -import org.apache.felix.schematizer.SchematizingConverter; import org.osgi.util.converter.Converter; import org.osgi.util.converter.StandardConverter; @@ -59,11 +58,22 @@ public NodeImpl rootNodeInternal() { } @Override - public Optional nodeAtPath( String absolutePath ) - { + public Optional nodeAtPath( String absolutePath ) { return Optional.ofNullable(nodes.get(absolutePath)); } + @Override + public Optional parentOf( Node aNode ) { + if (aNode == null || aNode.absolutePath() == null) + return Optional.empty(); + + NodeImpl node = nodes.get(aNode.absolutePath()); + if (node == null) + return Optional.empty(); + + return Optional.ofNullable( node.parent() ); + } + void add(NodeImpl node) { nodes.put(node.absolutePath(), node); } @@ -109,6 +119,7 @@ public Collection valuesAt(String path, Object object) { return valuesAt("", map, contexts, 0); } + @SuppressWarnings( { "rawtypes", "unchecked" } ) private Collection valuesAt(String context, Map objectMap, List contexts, int currentIndex) { List result = new ArrayList<>(); String currentContext = contexts.get(currentIndex); @@ -140,12 +151,13 @@ private Collection valuesAt(String context, Map objectMap, Li return result; } + @SuppressWarnings( "rawtypes" ) private Object convertToType( String path, Map map ) { Optional node = nodeAtPath(path); if (!node.isPresent()) return map; - Object result = new StandardConverter().convert(map).sourceAs(DTO.class).to(node.get().type()); + Object result = new StandardConverter().convert(map).to(node.get().type()); return result; } diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java index 8e3a710c12d..90dfcf00fdb 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchematizerImpl.java @@ -188,8 +188,8 @@ private static SchemaImpl schematizeDTO( rootNode = new NodeImpl(contextPath, targetCls, false, contextPath + "/"); schema.add(rootNode); Map m = createMapFromDTO(name, targetCls, ref, contextPath, rules, schematizer); - processNodeParentsAndFields(rootNode, m); m.values().stream().filter(v -> v.absolutePath().equals(rootNode.absolutePath() + v.name())).forEach(v -> rootNode.add(v)); + associateChildNodes( rootNode ); schema.add(m); return schema; } @@ -410,22 +410,6 @@ public Type apply(String className) { } } - static private void processNodeParentsAndFields(NodeImpl rootNode, Map map) { - for(NodeImpl n : map.values()) { - if (n.parent() != null) - continue; - n.parent(rootNode); - String fieldName = n.name(); - Class parentClass = rawClassOf(rootNode.type()); - try { - Field field = parentClass.getField(fieldName); - n.field(field); - } catch ( NoSuchFieldException e ) { - e.printStackTrace(); - } - } - } - static private void associateChildNodes(NodeImpl rootNode) { for (NodeImpl child: rootNode.childrenInternal().values()) { child.parent(rootNode); diff --git a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java index e4f18a90ef7..38dd9b0bc4b 100644 --- a/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java +++ b/converter/schematizer/src/test/java/org/apache/felix/schematizer/impl/SchematizerServiceTest.java @@ -289,6 +289,45 @@ public void testVisitor() { assertEquals("::::count::embedded::alpha::marco::polo::ping::pong", sb.toString()); } + @Test + public void testGetParentNode() { + Optional opt = schematizer + .rule("MyDTO", new TypeReference(){}) + .rule("MyDTO", "/embedded", new TypeReference(){}) + .get("MyDTO"); + + assertTrue(opt.isPresent()); + Schema s = opt.get(); + assertNotNull(s); + Optional embeddedNode = s.nodeAtPath("/embedded/marco"); + assertTrue(embeddedNode.isPresent()); + Optional parentNode = s.parentOf(embeddedNode.get()); + assertTrue(parentNode.isPresent()); + Optional grandparentNode = s.parentOf(parentNode.get()); + assertTrue(grandparentNode.isPresent()); + assertEquals("/", grandparentNode.get().absolutePath()); + } + + @Test + public void testGetParentNode2() { + Optional opt = schematizer + .rule("MyDTO", new TypeReference>>(){}) + .rule("MyDTO", "/embedded", new TypeReference>(){}) + .rule("MyDTO", "/embedded/value", String.class) + .get("MyDTO"); + + assertTrue(opt.isPresent()); + Schema s = opt.get(); + assertNotNull(s); + Optional embeddedNode = s.nodeAtPath("/embedded/value"); + assertTrue(embeddedNode.isPresent()); + Optional parentNode = s.parentOf(embeddedNode.get()); + assertTrue(parentNode.isPresent()); + Optional grandparentNode = s.parentOf(parentNode.get()); + assertTrue(grandparentNode.isPresent()); + assertEquals("/", grandparentNode.get().absolutePath()); + } + private void assertNodeEquals(String name, String path, boolean isCollection, Object type, boolean fieldNotNull, Node node) { assertNotNull(node); assertEquals(name, node.name()); From a84f08bfa95a802b1cc5a834215ef9c504df1526 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Wed, 11 Jan 2017 15:44:33 -0800 Subject: [PATCH 16/18] Fixed a bug relating to DTOs embedded in a Map --- .../felix/converter/impl/ConvertingImpl.java | 5 ++- .../felix/converter/impl/ConverterTest.java | 39 +++++++++++++++++++ .../apache/felix/converter/impl/MyDTO8.java | 32 +++++++++++++++ .../felix/schematizer/impl/SchemaImpl.java | 7 ++-- 4 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 converter/converter/src/test/java/org/apache/felix/converter/impl/MyDTO8.java diff --git a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java index 2fa0edc3d2e..eb94d802154 100644 --- a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java +++ b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java @@ -774,6 +774,9 @@ private void handleField(Object obj, Field field, Set handledFields, Map Object fVal = field.get(obj); if (isMapType(field.getType())) { fVal = converter.convert(fVal).key(ka).to(Map.class); + // Depends on whether or not a copy to Map is supposed to be a "deep" copy +// } else if (fVal instanceof DTO) { +// fVal = converter.convert(fVal).sourceAs(DTO.class).to(Map.class); } result.put(fn, fVal); @@ -843,7 +846,7 @@ private static void handleInterfaceMethod(Object obj, Method md, Set inv } private Map mapView(Object obj, Class sourceCls, InternalConverter converter) { - if (Map.class.isAssignableFrom(sourceCls)) + if (Map.class.isAssignableFrom(sourceCls) || (DTO.class.equals(sourceCls) && obj instanceof Map)) return (Map) obj; else if (Dictionary.class.isAssignableFrom(sourceCls)) return null; // TODO diff --git a/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java b/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java index b33b4110118..d3e15f5dfbc 100644 --- a/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java +++ b/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java @@ -490,6 +490,45 @@ public void testDTO2Map2() { assertEquals(Alpha.A, e.get("alpha")); } + @Test + public void testDTO2Map3() { + MyEmbeddedDTO embedded2 = new MyEmbeddedDTO(); + embedded2.marco = "hohoho"; + embedded2.polo = Long.MAX_VALUE; + embedded2.alpha = Alpha.A; + + MyDTOWithMethods embedded = new MyDTOWithMethods(); + embedded.ping = "lalala"; + embedded.pong = Long.MIN_VALUE; + embedded.count = Count.ONE; + embedded.embedded = embedded2; + + MyDTO8 dto = new MyDTO8(); + dto.ping = "lalala"; + dto.pong = Long.MIN_VALUE; + dto.count = MyDTO8.Count.ONE; + dto.embedded = embedded; + + @SuppressWarnings("rawtypes") + Map m = converter.convert(dto).sourceAs(DTO.class).to(Map.class); + assertEquals(4, m.size()); + assertEquals("lalala", m.get("ping")); + assertEquals(Long.MIN_VALUE, m.get("pong")); + assertEquals(MyDTO8.Count.ONE, m.get("count")); + assertNotNull(m.get("embedded")); + assertTrue(m.get( "embedded" ) instanceof MyDTOWithMethods); + MyDTOWithMethods e = (MyDTOWithMethods)m.get("embedded"); + assertEquals("lalala", e.ping); + assertEquals(Long.MIN_VALUE, e.pong); + assertEquals(Count.ONE, e.count); + assertNotNull(e.embedded); + assertTrue(e.embedded instanceof MyEmbeddedDTO); + MyEmbeddedDTO e2 = (MyEmbeddedDTO)e.embedded; + assertEquals("hohoho", e2.marco); + assertEquals(Long.MAX_VALUE, e2.polo); + assertEquals(Alpha.A, e2.alpha); + } + @Test @SuppressWarnings({ "rawtypes", "unchecked" }) public void testDTOFieldShadowing() { MySubDTO dto = new MySubDTO(); diff --git a/converter/converter/src/test/java/org/apache/felix/converter/impl/MyDTO8.java b/converter/converter/src/test/java/org/apache/felix/converter/impl/MyDTO8.java new file mode 100644 index 00000000000..bc94dc1e4e2 --- /dev/null +++ b/converter/converter/src/test/java/org/apache/felix/converter/impl/MyDTO8.java @@ -0,0 +1,32 @@ +/* + * 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.dto.DTO; + +public class MyDTO8 extends DTO { + public enum Count { ONE, TWO, THREE } + + public Count count; + + public String ping; + + public long pong; + + public MyDTOWithMethods embedded; +} + diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java index c0dd57c6ae5..f2b12ba796b 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java @@ -104,7 +104,7 @@ public void visit(NodeVisitor visitor) { public Collection valuesAt(String path, Object object) { final Converter converter = new StandardConverter(); @SuppressWarnings( "unchecked" ) - final Map map = (Map)converter.convert( object ).to( Map.class ); + final Map map = (Map)converter.convert(object).sourceAs(DTO.class).to( Map.class ); if (map == null || map.isEmpty()) return Collections.emptyList(); @@ -125,7 +125,6 @@ private Collection valuesAt(String context, Map objectMap, Li String currentContext = contexts.get(currentIndex); Object o = objectMap.get(currentContext); if (o instanceof List) { - @SuppressWarnings( "unchecked" ) List l = (List)o; if (currentIndex == contexts.size() - 1) { // We are at the end, so just add the collection @@ -143,7 +142,7 @@ private Collection valuesAt(String context, Map objectMap, Li return result; } - result.addAll( valuesAt( currentContext, (Map)o, contexts, ++currentIndex ) ); + result.addAll(valuesAt( currentContext, (Map)o, contexts, ++currentIndex)); } else { result.add(o); } @@ -157,7 +156,7 @@ private Object convertToType( String path, Map map ) { if (!node.isPresent()) return map; - Object result = new StandardConverter().convert(map).to(node.get().type()); + Object result = new StandardConverter().convert(map).targetAs(DTO.class).to(node.get().type()); return result; } From 794878dbcaf61721d8232c4de79db37e60fb7c3c Mon Sep 17 00:00:00 2001 From: David Leangen Date: Wed, 11 Jan 2017 20:47:12 -0800 Subject: [PATCH 17/18] Additional bug fixes --- .../felix/converter/impl/ConvertingImpl.java | 2 +- .../felix/schematizer/impl/SchemaImpl.java | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java index eb94d802154..b44577186c1 100644 --- a/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java +++ b/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java @@ -846,7 +846,7 @@ private static void handleInterfaceMethod(Object obj, Method md, Set inv } private Map mapView(Object obj, Class sourceCls, InternalConverter converter) { - if (Map.class.isAssignableFrom(sourceCls) || (DTO.class.equals(sourceCls) && obj instanceof Map)) + if (Map.class.isAssignableFrom(sourceCls) || (DTO.class.isAssignableFrom(sourceCls) && obj instanceof Map)) return (Map) obj; else if (Dictionary.class.isAssignableFrom(sourceCls)) return null; // TODO diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java index f2b12ba796b..2a7ebcd7e51 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java @@ -24,12 +24,14 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.apache.felix.schematizer.Node; -import org.apache.felix.schematizer.Node.DTO; import org.apache.felix.schematizer.NodeVisitor; import org.apache.felix.schematizer.Schema; +import org.osgi.dto.DTO; import org.osgi.util.converter.Converter; import org.osgi.util.converter.StandardConverter; @@ -132,7 +134,7 @@ private Collection valuesAt(String context, Map objectMap, Li return result; } - currentContext = "/" + contexts.get(++currentIndex); + currentContext = pathFrom(contexts, ++currentIndex); for (Object o2 : l) result.addAll(valuesAt(currentContext, o2)); } else if (o instanceof Map){ @@ -143,6 +145,8 @@ private Collection valuesAt(String context, Map objectMap, Li } result.addAll(valuesAt( currentContext, (Map)o, contexts, ++currentIndex)); + } else if (currentIndex < contexts.size() - 1) { + result.addAll(valuesAt(pathFrom(contexts, ++currentIndex), o)); } else { result.add(o); } @@ -169,4 +173,12 @@ private List convertToType( String path, List list ) { .map( v -> new StandardConverter().convert(v).sourceAs(DTO.class).to(node.get().type())) .collect( Collectors.toList() ); } + + private String pathFrom(List contexts, int index) { + return IntStream.range(0, contexts.size()) + .filter( i -> i >= index ) + .mapToObj( i -> contexts.get(i) ) + .reduce( "", (s1,s2) -> s1 + "/" + s2 ); + + } } \ No newline at end of file From 4329c58ebef57f2e94f1dd3a43c3e313ff29cce8 Mon Sep 17 00:00:00 2001 From: David Leangen Date: Sat, 14 Jan 2017 07:59:03 -0800 Subject: [PATCH 18/18] Fixed a few more bugs in valuesAt --- .../felix/schematizer/impl/SchemaImpl.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java index 2a7ebcd7e51..f6fd202cbc1 100644 --- a/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java +++ b/converter/schematizer/src/main/java/org/apache/felix/schematizer/impl/SchemaImpl.java @@ -125,28 +125,37 @@ public Collection valuesAt(String path, Object object) { private Collection valuesAt(String context, Map objectMap, List contexts, int currentIndex) { List result = new ArrayList<>(); String currentContext = contexts.get(currentIndex); + if (objectMap == null) + return result; Object o = objectMap.get(currentContext); if (o instanceof List) { List l = (List)o; if (currentIndex == contexts.size() - 1) { // We are at the end, so just add the collection - result.add(convertToType("/" + currentContext, l)); + result.add(convertToType(pathFrom(contexts, 0), l)); return result; } currentContext = pathFrom(contexts, ++currentIndex); for (Object o2 : l) - result.addAll(valuesAt(currentContext, o2)); + { + final Converter converter = new StandardConverter(); + final Map m = (Map)converter.convert(o2).sourceAs(DTO.class).to( Map.class ); + result.addAll( valuesAt( currentContext, m, contexts, currentIndex ) ); + } } else if (o instanceof Map){ if (currentIndex == contexts.size() - 1) { // We are at the end, so just add the result - result.add(convertToType("/" + currentContext, (Map)o)); + result.add(convertToType(pathFrom(contexts, 0), (Map)o)); return result; } result.addAll(valuesAt( currentContext, (Map)o, contexts, ++currentIndex)); } else if (currentIndex < contexts.size() - 1) { - result.addAll(valuesAt(pathFrom(contexts, ++currentIndex), o)); + final Converter converter = new StandardConverter(); + final Map m = (Map)converter.convert(o).sourceAs(DTO.class).to(Map.class); + currentContext = pathFrom(contexts, ++currentIndex); + result.addAll(valuesAt( currentContext, m, contexts, currentIndex )); } else { result.add(o); }