TAMAYA-74 Added support for type narowwing on property conversion.
Project: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/commit/4d560984 Tree: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/tree/4d560984 Diff: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/diff/4d560984 Branch: refs/heads/master Commit: 4d560984822e4ccc0b8bae0b00a98e278518a39e Parents: eef4efe Author: Anatole Tresch <atsti...@java.net> Authored: Sun Jun 14 00:17:03 2015 +0200 Committer: Anatole Tresch <atsti...@java.net> Committed: Sun Jun 14 00:17:03 2015 +0200 ---------------------------------------------------------------------- .../core/internal/DefaultServiceContext.java | 21 ++- .../core/internal/PropertyConverterManager.java | 186 ++++++++++++++----- .../java/org/apache/tamaya/core/internal/A.java | 29 +++ .../java/org/apache/tamaya/core/internal/B.java | 29 +++ .../java/org/apache/tamaya/core/internal/C.java | 56 ++++++ .../tamaya/core/internal/CTestConverter.java | 31 ++++ .../internal/PropertyConverterManagerTest.java | 85 +++++++++ .../org.apache.tamaya.spi.PropertyConverter | 19 ++ .../core/internal/DefaultServiceContext.java | 20 +- .../core/internal/PropertyConverterManager.java | 131 ++++++++++--- .../java/org/apache/tamaya/core/internal/A.java | 29 +++ .../java/org/apache/tamaya/core/internal/B.java | 29 +++ .../java/org/apache/tamaya/core/internal/C.java | 56 ++++++ .../tamaya/core/internal/CTestConverter.java | 31 ++++ .../internal/PropertyConverterManagerTest.java | 85 +++++++++ .../org.apache.tamaya.spi.PropertyConverter | 19 ++ 16 files changed, 778 insertions(+), 78 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java7/core/src/main/java/org/apache/tamaya/core/internal/DefaultServiceContext.java ---------------------------------------------------------------------- diff --git a/java7/core/src/main/java/org/apache/tamaya/core/internal/DefaultServiceContext.java b/java7/core/src/main/java/org/apache/tamaya/core/internal/DefaultServiceContext.java index 2e59fa2..15bbcef 100644 --- a/java7/core/src/main/java/org/apache/tamaya/core/internal/DefaultServiceContext.java +++ b/java7/core/src/main/java/org/apache/tamaya/core/internal/DefaultServiceContext.java @@ -90,6 +90,20 @@ public final class DefaultServiceContext implements ServiceContext { return previousServices != null ? previousServices : services; } + /** + * Checks the given instance for a @Priority annotation. If present the annotation's value s evaluated. If no such + * annotation is present, a default priority is returned (1); + * @param o the instance, not null. + * @return a priority, by default 1. + */ + public static int getPriority(Object o){ + int prio = 1; //X TODO discuss default priority + Priority priority = o.getClass().getAnnotation(Priority.class); + if (priority != null) { + prio = priority.value(); + } + return prio; + } /** * @param services to scan @@ -111,12 +125,7 @@ public final class DefaultServiceContext implements ServiceContext { T highestService = null; for (T service : services) { - int prio = 1; //X TODO discuss default priority - Priority priority = service.getClass().getAnnotation(Priority.class); - if (priority != null) { - prio = priority.value(); - } - + int prio = getPriority(service); if (highestPriority == null || highestPriority < prio) { highestService = service; highestPriorityServiceCount = 1; http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java7/core/src/main/java/org/apache/tamaya/core/internal/PropertyConverterManager.java ---------------------------------------------------------------------- diff --git a/java7/core/src/main/java/org/apache/tamaya/core/internal/PropertyConverterManager.java b/java7/core/src/main/java/org/apache/tamaya/core/internal/PropertyConverterManager.java index 31a05b2..7fe6b11 100644 --- a/java7/core/src/main/java/org/apache/tamaya/core/internal/PropertyConverterManager.java +++ b/java7/core/src/main/java/org/apache/tamaya/core/internal/PropertyConverterManager.java @@ -24,6 +24,7 @@ import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -45,12 +46,38 @@ import org.apache.tamaya.spi.ServiceContextManager; * This class is thread-safe. */ public class PropertyConverterManager { - /** The logger used. */ + /** + * The logger used. + */ private static final Logger LOG = Logger.getLogger(PropertyConverterManager.class.getName()); - /** The registered converters. */ + /** + * The registered converters. + */ private Map<TypeLiteral<?>, List<PropertyConverter<?>>> converters = new ConcurrentHashMap<>(); - /** The lock used. */ + /** + * The transitive converters. + */ + private Map<TypeLiteral<?>, List<PropertyConverter<?>>> transitiveConverters = new ConcurrentHashMap<>(); + /** + * The lock used. + */ private ReadWriteLock lock = new ReentrantReadWriteLock(); + + private static final Comparator<Object> PRIORITY_COMPARATOR = new Comparator<Object>() { + + @Override + public int compare(Object o1, Object o2) { + int prio = DefaultServiceContext.getPriority(o1) - DefaultServiceContext.getPriority(o2); + if (prio < 0) { + return 1; + } else if (prio > 0) { + return -1; + } else { + return o1.getClass().getSimpleName().compareTo(o2.getClass().getSimpleName()); + } + } + }; + /** * Constructor. */ @@ -68,7 +95,7 @@ public class PropertyConverterManager { * Registers the default converters provided out of the box. */ protected void initConverters() { - for(PropertyConverter conv: ServiceContextManager.getServiceContext().getServices(PropertyConverter.class)){ + for (PropertyConverter conv : ServiceContextManager.getServiceContext().getServices(PropertyConverter.class)) { Type type = TypeLiteral.getGenericInterfaceTypeParameters(conv.getClass(), PropertyConverter.class)[0]; register(TypeLiteral.of(type), conv); } @@ -86,13 +113,48 @@ public class PropertyConverterManager { Lock writeLock = lock.writeLock(); try { writeLock.lock(); - List<PropertyConverter<T>> converters = List.class.cast(this.converters.get(targetType)); + List converters = List.class.cast(this.converters.get(targetType)); List<PropertyConverter<?>> newConverters = new ArrayList<>(); if (converters != null) { newConverters.addAll(converters); } newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); this.converters.put(targetType, Collections.unmodifiableList(newConverters)); + // evaluate transitive closure for all inherited supertypes and implemented interfaces + // direct implemented interfaces + for (Class<?> ifaceType : targetType.getRawType().getInterfaces()) { + converters = List.class.cast(this.transitiveConverters.get(ifaceType)); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(ifaceType), Collections.unmodifiableList(newConverters)); + } + Class<?> superClass = targetType.getRawType().getSuperclass(); + while (superClass != null && !superClass.equals(Object.class)) { + converters = List.class.cast(this.transitiveConverters.get(superClass)); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(superClass), Collections.unmodifiableList(newConverters)); + for (Class<?> ifaceType : superClass.getInterfaces()) { + converters = List.class.cast(this.transitiveConverters.get(ifaceType)); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(ifaceType), Collections.unmodifiableList(newConverters)); + } + superClass = superClass.getSuperclass(); + } } finally { writeLock.unlock(); } @@ -105,8 +167,10 @@ public class PropertyConverterManager { * @return true, if a converter for the given type is registered, or a default one can be created. */ public boolean isTargetTypeSupported(TypeLiteral<?> targetType) { - return converters.containsKey(targetType) - || createDefaultPropertyConverter(targetType) != null; + if (converters.containsKey(targetType) || transitiveConverters.containsKey(targetType)) { + return true; + } + return createDefaultPropertyConverter(targetType) != null; } /** @@ -130,7 +194,26 @@ public class PropertyConverterManager { /** * Get the list of all current registered converters for the given target type. * If not converters are registered, they component tries to create and register a dynamic - * converter based on String costructor or static factory methods available. + * converter based on String costructor or static factory methods available.<br/> + * The converters provided are of the following type and returned in the following order: + * <ul> + * <li>Converters mapped explicitly to the required target type are returned first, ordered + * by decreasing priority. This means, if explicit converters are registered these are used + * primarly for converting a value.</li> + * <li>The target type of each explicitly registered converter also can be transitively mapped to + * 1) all directly implemented interfaces, 2) all its superclasses (except Object), 3) all the interfaces + * implemented by its superclasses. These groups of transitive converters is returned similarly in the + * order as mentioned, whereas also here a priority based decreasing ordering is applied.</li> + * <li>java.lang wrapper classes and native types are automatically mapped.</li> + * <li>If no explicit converters are registered, for Enum types a default implementation is provided that + * compares the configuration values with the different enum members defined (cases sensitive mapping).</li> + * </ul> + * + * So given that list above directly registered mappings always are tried first, before any transitive mapping + * should be used. Also in all cases @Priority annotations are honored for ordering of the converters in place. + * Transitive conversion is supported for all directly implemented interfaces (including inherited ones) and + * the inheritance hierarchy (exception Object). Superinterfaces of implemented interfaces are ignored. + * * * @param targetType the target type, not null. * @param <T> the type class @@ -139,7 +222,9 @@ public class PropertyConverterManager { */ public <T> List<PropertyConverter<T>> getPropertyConverters(TypeLiteral<T> targetType) { Lock readLock = lock.readLock(); + List<PropertyConverter<T>> converterList = new ArrayList<>(); List<PropertyConverter<T>> converters; + // direct mapped converters try { readLock.lock(); converters = List.class.cast(this.converters.get(targetType)); @@ -147,10 +232,21 @@ public class PropertyConverterManager { readLock.unlock(); } if (converters != null) { - return converters; + converterList.addAll(converters); + } + // transitive converter + try { + readLock.lock(); + converters = List.class.cast(this.transitiveConverters.get(targetType)); + } finally { + readLock.unlock(); + } + if (converters != null) { + converterList.addAll(converters); } + // handling of java.lang wrapper classes TypeLiteral<T> boxedType = mapBoxedType(targetType); - if(boxedType!=null){ + if (boxedType != null) { try { readLock.lock(); converters = List.class.cast(this.converters.get(boxedType)); @@ -158,79 +254,83 @@ public class PropertyConverterManager { readLock.unlock(); } if (converters != null) { - return converters; + converterList.addAll(converters); } } - PropertyConverter<T> defaultConverter = createDefaultPropertyConverter(targetType); - if (defaultConverter != null) { - register(targetType, defaultConverter); - try { - readLock.lock(); - converters = List.class.cast(this.converters.get(targetType)); - } finally { - readLock.unlock(); + if (converterList.isEmpty()) { + // adding any converters created on the fly, e.g. for enum types. + PropertyConverter<T> defaultConverter = createDefaultPropertyConverter(targetType); + if (defaultConverter != null) { + register(targetType, defaultConverter); + try { + readLock.lock(); + converters = List.class.cast(this.converters.get(targetType)); + } finally { + readLock.unlock(); + } + } + if (converters != null) { + converterList.addAll(converters); } } - if (converters != null) { - return converters; - } - return Collections.emptyList(); + return converterList; } /** * Maps native types to the corresponding boxed types. + * * @param targetType the native type. - * @param <T> the type + * @param <T> the type * @return the boxed type, or null. */ private <T> TypeLiteral<T> mapBoxedType(TypeLiteral<T> targetType) { Type parameterType = targetType.getType(); - if(parameterType == int.class){ + if (parameterType == int.class) { return TypeLiteral.class.cast(TypeLiteral.of(Integer.class)); } - if(parameterType == short.class){ + if (parameterType == short.class) { return TypeLiteral.class.cast(TypeLiteral.of(Short.class)); } - if(parameterType == byte.class){ + if (parameterType == byte.class) { return TypeLiteral.class.cast(TypeLiteral.of(Byte.class)); } - if(parameterType == long.class){ + if (parameterType == long.class) { return TypeLiteral.class.cast(TypeLiteral.of(Long.class)); } - if(parameterType == boolean.class){ + if (parameterType == boolean.class) { return TypeLiteral.class.cast(TypeLiteral.of(Boolean.class)); } - if(parameterType == char.class){ + if (parameterType == char.class) { return TypeLiteral.class.cast(TypeLiteral.of(Character.class)); } - if(parameterType == float.class){ + if (parameterType == float.class) { return TypeLiteral.class.cast(TypeLiteral.of(Float.class)); } - if(parameterType == double.class){ + if (parameterType == double.class) { return TypeLiteral.class.cast(TypeLiteral.of(Double.class)); } - if(parameterType == int[].class){ + if (parameterType == int[].class) { return TypeLiteral.class.cast(TypeLiteral.of(Integer[].class)); } - if(parameterType == short[].class){ + if (parameterType == short[].class) { return TypeLiteral.class.cast(TypeLiteral.of(Short[].class)); } - if(parameterType == byte[].class){ + if (parameterType == byte[].class) { return TypeLiteral.class.cast(TypeLiteral.of(Byte[].class)); } - if(parameterType == long[].class){ + if (parameterType == long[].class) { return TypeLiteral.class.cast(TypeLiteral.of(Long[].class)); } - if(parameterType == boolean.class){ + if (parameterType == boolean.class) { return TypeLiteral.class.cast(TypeLiteral.of(Boolean.class)); } - if(parameterType == char[].class){ + if (parameterType == char[].class) { return TypeLiteral.class.cast(TypeLiteral.of(Character[].class)); } - if(parameterType == float[].class){ + if (parameterType == float[].class) { return TypeLiteral.class.cast(TypeLiteral.of(Float[].class)); } - if(parameterType == double[].class){ + if (parameterType == double[].class) { return TypeLiteral.class.cast(TypeLiteral.of(Double[].class)); } return null; @@ -244,7 +344,7 @@ public class PropertyConverterManager { * @return a new converter, or null. */ protected <T> PropertyConverter<T> createDefaultPropertyConverter(final TypeLiteral<T> targetType) { - if(Enum.class.isAssignableFrom(targetType.getRawType())){ + if (Enum.class.isAssignableFrom(targetType.getRawType())) { return new EnumConverter<T>(targetType.getRawType()); } PropertyConverter<T> converter = null; @@ -285,7 +385,7 @@ public class PropertyConverterManager { } }; } catch (Exception e) { - LOG.finest("Failed to construct instance of type: " + targetType.getRawType().getName()+": " + e); + LOG.finest("Failed to construct instance of type: " + targetType.getRawType().getName() + ": " + e); } } return converter; @@ -305,7 +405,7 @@ public class PropertyConverterManager { m = type.getDeclaredMethod(name, String.class); return m; } catch (NoSuchMethodException | RuntimeException e) { - LOG.finest("No such factory method found on type: " + type.getName()+", methodName: " + name); + LOG.finest("No such factory method found on type: " + type.getName() + ", methodName: " + name); } } return null; http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java7/core/src/test/java/org/apache/tamaya/core/internal/A.java ---------------------------------------------------------------------- diff --git a/java7/core/src/test/java/org/apache/tamaya/core/internal/A.java b/java7/core/src/test/java/org/apache/tamaya/core/internal/A.java new file mode 100644 index 0000000..aa77a5d --- /dev/null +++ b/java7/core/src/test/java/org/apache/tamaya/core/internal/A.java @@ -0,0 +1,29 @@ +/* + * 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.tamaya.core.internal; + +/** + * Test class for testing transitively evaluated property converters. + */ +public class A implements AutoCloseable{ + @Override + public void close() throws Exception { + + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java7/core/src/test/java/org/apache/tamaya/core/internal/B.java ---------------------------------------------------------------------- diff --git a/java7/core/src/test/java/org/apache/tamaya/core/internal/B.java b/java7/core/src/test/java/org/apache/tamaya/core/internal/B.java new file mode 100644 index 0000000..31bafb6 --- /dev/null +++ b/java7/core/src/test/java/org/apache/tamaya/core/internal/B.java @@ -0,0 +1,29 @@ +/* + * 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.tamaya.core.internal; + +/** + * Test class for testing transitively evaluated property converters. + */ +public class B extends A implements Runnable{ + @Override + public void run() { + + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java7/core/src/test/java/org/apache/tamaya/core/internal/C.java ---------------------------------------------------------------------- diff --git a/java7/core/src/test/java/org/apache/tamaya/core/internal/C.java b/java7/core/src/test/java/org/apache/tamaya/core/internal/C.java new file mode 100644 index 0000000..aad1ef9 --- /dev/null +++ b/java7/core/src/test/java/org/apache/tamaya/core/internal/C.java @@ -0,0 +1,56 @@ +/* + * 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.tamaya.core.internal; + +import java.io.IOException; +import java.nio.CharBuffer; + +/** + * Test class for testing transitively evaluated property converters. + */ +public class C extends B implements Readable{ + + private String inValue; + + public C(String inValue){ + this.inValue = inValue; + } + + @Override + public int read(CharBuffer cb) throws IOException { + return 0; + } + + /** + * Returns the input value, set on creation. Used for test assertion. + * @return the in value. + */ + public String getInValue() { + return inValue; + } + + @Override + public String toString() { + return "C{" + + "inValue='" + inValue + '\'' + + '}'; + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java7/core/src/test/java/org/apache/tamaya/core/internal/CTestConverter.java ---------------------------------------------------------------------- diff --git a/java7/core/src/test/java/org/apache/tamaya/core/internal/CTestConverter.java b/java7/core/src/test/java/org/apache/tamaya/core/internal/CTestConverter.java new file mode 100644 index 0000000..62b8c9d --- /dev/null +++ b/java7/core/src/test/java/org/apache/tamaya/core/internal/CTestConverter.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.core.internal; + +import org.apache.tamaya.spi.PropertyConverter; + +/** + * Created by Anatole on 13.06.2015. + */ +public class CTestConverter implements PropertyConverter<C>{ + @Override + public C convert(String value) { + return new C(value); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java7/core/src/test/java/org/apache/tamaya/core/internal/PropertyConverterManagerTest.java ---------------------------------------------------------------------- diff --git a/java7/core/src/test/java/org/apache/tamaya/core/internal/PropertyConverterManagerTest.java b/java7/core/src/test/java/org/apache/tamaya/core/internal/PropertyConverterManagerTest.java index bd9b97f..9ec6076 100644 --- a/java7/core/src/test/java/org/apache/tamaya/core/internal/PropertyConverterManagerTest.java +++ b/java7/core/src/test/java/org/apache/tamaya/core/internal/PropertyConverterManagerTest.java @@ -57,6 +57,90 @@ public class PropertyConverterManagerTest { assertThat(((MyType)result).getValue(), equalTo("IN")); } + @Test + public void testDirectConverterMapping(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<C>> converters = List.class.cast(manager.getPropertyConverters(TypeLiteral.of(C.class))); + assertThat(converters, hasSize(1)); + + PropertyConverter<C> converter = converters.get(0); + C result = converter.convert("testDirectConverterMapping"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testDirectConverterMapping")); + } + + @Test + public void testDirectSuperclassConverterMapping(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<B>> converters = List.class.cast(manager.getPropertyConverters(TypeLiteral.of(B.class))); + assertThat(converters, hasSize(1)); + + PropertyConverter<B> converter = converters.get(0); + B result = converter.convert("testDirectSuperclassConverterMapping"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testDirectSuperclassConverterMapping")); + } + + @Test + public void testTransitiveSuperclassConverterMapping(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<A>> converters = List.class.cast(manager.getPropertyConverters(TypeLiteral.of(A.class))); + assertThat(converters, hasSize(1)); + + PropertyConverter<A> converter = converters.get(0); + A result = converter.convert("testTransitiveSuperclassConverterMapping"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testTransitiveSuperclassConverterMapping")); + } + + @Test + public void testDirectInterfaceMapping(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<Readable>> converters = List.class.cast(manager.getPropertyConverters(TypeLiteral.of(Readable.class))); + assertThat(converters, hasSize(1)); + + PropertyConverter<Readable> converter = converters.get(0); + Readable result = converter.convert("testDirectInterfaceMapping"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testDirectInterfaceMapping")); + } + + @Test + public void testTransitiveInterfaceMapping1(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<Runnable>> converters = List.class.cast(manager.getPropertyConverters(TypeLiteral.of(Runnable.class))); + assertThat(converters, hasSize(1)); + + PropertyConverter<Runnable> converter = converters.get(0); + Runnable result = converter.convert("testTransitiveInterfaceMapping1"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testTransitiveInterfaceMapping1")); + } + + @Test + public void testTransitiveInterfaceMapping2(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<AutoCloseable>> converters = List.class.cast(manager.getPropertyConverters(TypeLiteral.of(AutoCloseable.class))); + assertThat(converters, hasSize(1)); + + PropertyConverter<AutoCloseable> converter = converters.get(0); + AutoCloseable result = converter.convert("testTransitiveInterfaceMapping2"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testTransitiveInterfaceMapping2")); + } + public static class MyType { private String typeValue; @@ -73,4 +157,5 @@ public class PropertyConverterManagerTest { } } + } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java7/core/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertyConverter ---------------------------------------------------------------------- diff --git a/java7/core/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertyConverter b/java7/core/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertyConverter new file mode 100644 index 0000000..d039696 --- /dev/null +++ b/java7/core/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertyConverter @@ -0,0 +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 current 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. +# +org.apache.tamaya.core.internal.CTestConverter \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java8/core/src/main/java/org/apache/tamaya/core/internal/DefaultServiceContext.java ---------------------------------------------------------------------- diff --git a/java8/core/src/main/java/org/apache/tamaya/core/internal/DefaultServiceContext.java b/java8/core/src/main/java/org/apache/tamaya/core/internal/DefaultServiceContext.java index 42c928b..7315ef1 100644 --- a/java8/core/src/main/java/org/apache/tamaya/core/internal/DefaultServiceContext.java +++ b/java8/core/src/main/java/org/apache/tamaya/core/internal/DefaultServiceContext.java @@ -89,6 +89,20 @@ public final class DefaultServiceContext implements ServiceContext { return previousServices != null ? previousServices : services; } + /** + * Checks the given instance for a @Priority annotation. If present the annotation's value s evaluated. If no such + * annotation is present, a default priority is returned (1); + * @param o the instance, not null. + * @return a priority, by default 1. + */ + public static int getPriority(Object o){ + int prio = 1; //X TODO discuss default priority + Priority priority = o.getClass().getAnnotation(Priority.class); + if (priority != null) { + prio = priority.value(); + } + return prio; + } /** * @param services to scan @@ -110,11 +124,7 @@ public final class DefaultServiceContext implements ServiceContext { T highestService = null; for (T service : services) { - int prio = 1; //X TODO discuss default priority - Priority priority = service.getClass().getAnnotation(Priority.class); - if (priority != null) { - prio = priority.value(); - } + int prio = getPriority(service); if (highestPriority == null || highestPriority < prio) { highestService = service; http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java8/core/src/main/java/org/apache/tamaya/core/internal/PropertyConverterManager.java ---------------------------------------------------------------------- diff --git a/java8/core/src/main/java/org/apache/tamaya/core/internal/PropertyConverterManager.java b/java8/core/src/main/java/org/apache/tamaya/core/internal/PropertyConverterManager.java index 333572e..3cb0656 100644 --- a/java8/core/src/main/java/org/apache/tamaya/core/internal/PropertyConverterManager.java +++ b/java8/core/src/main/java/org/apache/tamaya/core/internal/PropertyConverterManager.java @@ -22,12 +22,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.StampedLock; @@ -48,8 +43,23 @@ public class PropertyConverterManager { private static final Logger LOG = Logger.getLogger(PropertyConverterManager.class.getName()); /** The registered converters. */ private Map<TypeLiteral<?>, List<PropertyConverter<?>>> converters = new ConcurrentHashMap<>(); + /** The transitive converters as evaluated based on the registered converters. */ + private Map<TypeLiteral<?>, List<PropertyConverter<?>>> transitiveConverters = new ConcurrentHashMap<>(); /** The lock used. */ private StampedLock lock = new StampedLock(); + + private static final Comparator<Object> PRIORITY_COMPARATOR = + (o1,o2) -> { + int prio = DefaultServiceContext.getPriority(o1) - DefaultServiceContext.getPriority(o2); + if(prio<0){ + return 1; + } else if(prio>0){ + return -1; + } else{ + return o1.getClass().getSimpleName().compareTo(o2.getClass().getSimpleName()); + } + }; + /** * Constructor. */ @@ -91,7 +101,42 @@ public class PropertyConverterManager { newConverters.addAll(converters); } newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); this.converters.put(targetType, Collections.unmodifiableList(newConverters)); + // evaluate transitive closure for all inherited supertypes and implemented interfaces + // direct implemented interfaces + for(Class<?> ifaceType: targetType.getRawType().getInterfaces()){ + converters = List.class.cast(this.transitiveConverters.get(ifaceType)); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(ifaceType), Collections.unmodifiableList(newConverters)); + } + Class<?> superClass = targetType.getRawType().getSuperclass(); + while(superClass!=null && !superClass.equals(Object.class)){ + converters = List.class.cast(this.transitiveConverters.get(superClass)); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(superClass), Collections.unmodifiableList(newConverters)); + for(Class<?> ifaceType: superClass.getInterfaces()){ + converters = List.class.cast(this.transitiveConverters.get(ifaceType)); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(ifaceType), Collections.unmodifiableList(newConverters)); + } + superClass = superClass.getSuperclass(); + } } finally { writeLock.unlock(); } @@ -104,14 +149,17 @@ public class PropertyConverterManager { * @return true, if a converter for the given type is registered, or a default one can be created. */ public boolean isTargetTypeSupported(TypeLiteral<?> targetType) { - return converters.containsKey(targetType) - || createDefaultPropertyConverter(targetType) != null; + if(converters.containsKey(targetType) || transitiveConverters.containsKey(targetType)){ + return true; + } + return createDefaultPropertyConverter(targetType) != null; } /** * Get a map of all property converters currently registered. This will not contain the converters that * may be created, when an instance is adapted, which provides a String constructor or compatible - * factory methods taking a single String instance. + * factory methods taking a single String instance. <br/> + * This will also NOT contain any transitive converter mappings as derived from the registered converters. * * @return the current map of instantiated and registered converters. * @see #createDefaultPropertyConverter(org.apache.tamaya.TypeLiteral) @@ -129,7 +177,25 @@ public class PropertyConverterManager { /** * Get the list of all current registered converters for the given target type. * If not converters are registered, they component tries to create and register a dynamic - * converter based on String costructor or static factory methods available. + * converter based on String costructor or static factory methods available.<br/> + * The converters provided are of the following type and returned in the following order: + * <ul> + * <li>Converters mapped explicitly to the required target type are returned first, ordered + * by decreasing priority. This means, if explicit converters are registered these are used + * primarly for converting a value.</li> + * <li>The target type of each explicitly registered converter also can be transitively mapped to + * 1) all directly implemented interfaces, 2) all its superclasses (except Object), 3) all the interfaces + * implemented by its superclasses. These groups of transitive converters is returned similarly in the + * order as mentioned, whereas also here a priority based decreasing ordering is applied.</li> + * <li>java.lang wrapper classes and native types are automatically mapped.</li> + * <li>If no explicit converters are registered, for Enum types a default implementation is provided that + * compares the configuration values with the different enum members defined (cases sensitive mapping).</li> + * </ul> + * + * So given that list above directly registered mappings always are tried first, before any transitive mapping + * should be used. Also in all cases @Priority annotations are honored for ordering of the converters in place. + * Transitive conversion is supported for all directly implemented interfaces (including inherited ones) and + * the inheritance hierarchy (exception Object). Superinterfaces of implemented interfaces are ignored. * * @param targetType the target type, not null. * @param <T> the type class @@ -137,8 +203,10 @@ public class PropertyConverterManager { * @see #createDefaultPropertyConverter(org.apache.tamaya.TypeLiteral) */ public <T> List<PropertyConverter<T>> getPropertyConverters(TypeLiteral<T> targetType) { + List<PropertyConverter<T>> converterList = new ArrayList<>(); Lock readLock = lock.asReadLock(); List<PropertyConverter<T>> converters; + // direct mapped converters try { readLock.lock(); converters = List.class.cast(this.converters.get(targetType)); @@ -146,8 +214,19 @@ public class PropertyConverterManager { readLock.unlock(); } if (converters != null) { - return converters; + converterList.addAll(converters); + } + // transitive converter + try { + readLock.lock(); + converters = List.class.cast(this.transitiveConverters.get(targetType)); + } finally { + readLock.unlock(); + } + if (converters != null) { + converterList.addAll(converters); } + // handling of java.lang wrapper classes TypeLiteral<T> boxedType = mapBoxedType(targetType); if(boxedType!=null){ try { @@ -157,23 +236,26 @@ public class PropertyConverterManager { readLock.unlock(); } if (converters != null) { - return converters; + converterList.addAll(converters); } } - PropertyConverter<T> defaultConverter = createDefaultPropertyConverter(targetType); - if (defaultConverter != null) { - register(targetType, defaultConverter); - try { - readLock.lock(); - converters = List.class.cast(this.converters.get(targetType)); - } finally { - readLock.unlock(); + if(converterList.isEmpty()) { + // adding any converters created on the fly, e.g. for enum types. + PropertyConverter<T> defaultConverter = createDefaultPropertyConverter(targetType); + if (defaultConverter != null) { + register(targetType, defaultConverter); + try { + readLock.lock(); + converters = List.class.cast(this.converters.get(targetType)); + } finally { + readLock.unlock(); + } + } + if (converters != null) { + converterList.addAll(converters); } } - if (converters != null) { - return converters; - } - return Collections.emptyList(); + return converterList; } /** @@ -304,4 +386,5 @@ public class PropertyConverterManager { return null; } + } http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java8/core/src/test/java/org/apache/tamaya/core/internal/A.java ---------------------------------------------------------------------- diff --git a/java8/core/src/test/java/org/apache/tamaya/core/internal/A.java b/java8/core/src/test/java/org/apache/tamaya/core/internal/A.java new file mode 100644 index 0000000..aa77a5d --- /dev/null +++ b/java8/core/src/test/java/org/apache/tamaya/core/internal/A.java @@ -0,0 +1,29 @@ +/* + * 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.tamaya.core.internal; + +/** + * Test class for testing transitively evaluated property converters. + */ +public class A implements AutoCloseable{ + @Override + public void close() throws Exception { + + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java8/core/src/test/java/org/apache/tamaya/core/internal/B.java ---------------------------------------------------------------------- diff --git a/java8/core/src/test/java/org/apache/tamaya/core/internal/B.java b/java8/core/src/test/java/org/apache/tamaya/core/internal/B.java new file mode 100644 index 0000000..31bafb6 --- /dev/null +++ b/java8/core/src/test/java/org/apache/tamaya/core/internal/B.java @@ -0,0 +1,29 @@ +/* + * 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.tamaya.core.internal; + +/** + * Test class for testing transitively evaluated property converters. + */ +public class B extends A implements Runnable{ + @Override + public void run() { + + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java8/core/src/test/java/org/apache/tamaya/core/internal/C.java ---------------------------------------------------------------------- diff --git a/java8/core/src/test/java/org/apache/tamaya/core/internal/C.java b/java8/core/src/test/java/org/apache/tamaya/core/internal/C.java new file mode 100644 index 0000000..aad1ef9 --- /dev/null +++ b/java8/core/src/test/java/org/apache/tamaya/core/internal/C.java @@ -0,0 +1,56 @@ +/* + * 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.tamaya.core.internal; + +import java.io.IOException; +import java.nio.CharBuffer; + +/** + * Test class for testing transitively evaluated property converters. + */ +public class C extends B implements Readable{ + + private String inValue; + + public C(String inValue){ + this.inValue = inValue; + } + + @Override + public int read(CharBuffer cb) throws IOException { + return 0; + } + + /** + * Returns the input value, set on creation. Used for test assertion. + * @return the in value. + */ + public String getInValue() { + return inValue; + } + + @Override + public String toString() { + return "C{" + + "inValue='" + inValue + '\'' + + '}'; + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java8/core/src/test/java/org/apache/tamaya/core/internal/CTestConverter.java ---------------------------------------------------------------------- diff --git a/java8/core/src/test/java/org/apache/tamaya/core/internal/CTestConverter.java b/java8/core/src/test/java/org/apache/tamaya/core/internal/CTestConverter.java new file mode 100644 index 0000000..62b8c9d --- /dev/null +++ b/java8/core/src/test/java/org/apache/tamaya/core/internal/CTestConverter.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.core.internal; + +import org.apache.tamaya.spi.PropertyConverter; + +/** + * Created by Anatole on 13.06.2015. + */ +public class CTestConverter implements PropertyConverter<C>{ + @Override + public C convert(String value) { + return new C(value); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java8/core/src/test/java/org/apache/tamaya/core/internal/PropertyConverterManagerTest.java ---------------------------------------------------------------------- diff --git a/java8/core/src/test/java/org/apache/tamaya/core/internal/PropertyConverterManagerTest.java b/java8/core/src/test/java/org/apache/tamaya/core/internal/PropertyConverterManagerTest.java index fb80e05..9b413aa 100644 --- a/java8/core/src/test/java/org/apache/tamaya/core/internal/PropertyConverterManagerTest.java +++ b/java8/core/src/test/java/org/apache/tamaya/core/internal/PropertyConverterManagerTest.java @@ -74,4 +74,89 @@ public class PropertyConverterManagerTest { } } + + @Test + public void testDirectConverterMapping(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<C>> converters = manager.getPropertyConverters(TypeLiteral.of(C.class)); + assertThat(converters, hasSize(1)); + + PropertyConverter<C> converter = converters.get(0); + C result = converter.convert("testDirectConverterMapping"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testDirectConverterMapping")); + } + + @Test + public void testDirectSuperclassConverterMapping(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<B>> converters = manager.getPropertyConverters(TypeLiteral.of(B.class)); + assertThat(converters, hasSize(1)); + + PropertyConverter<B> converter = converters.get(0); + B result = converter.convert("testDirectSuperclassConverterMapping"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testDirectSuperclassConverterMapping")); + } + + @Test + public void testTransitiveSuperclassConverterMapping(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<A>> converters = manager.getPropertyConverters(TypeLiteral.of(A.class)); + assertThat(converters, hasSize(1)); + + PropertyConverter<A> converter = converters.get(0); + A result = converter.convert("testTransitiveSuperclassConverterMapping"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testTransitiveSuperclassConverterMapping")); + } + + @Test + public void testDirectInterfaceMapping(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<Readable>> converters = manager.getPropertyConverters(TypeLiteral.of(Readable.class)); + assertThat(converters, hasSize(1)); + + PropertyConverter<Readable> converter = converters.get(0); + Readable result = converter.convert("testDirectInterfaceMapping"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testDirectInterfaceMapping")); + } + + @Test + public void testTransitiveInterfaceMapping1(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<Runnable>> converters = manager.getPropertyConverters(TypeLiteral.of(Runnable.class)); + assertThat(converters, hasSize(1)); + + PropertyConverter<Runnable> converter = converters.get(0); + Runnable result = converter.convert("testTransitiveInterfaceMapping1"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testTransitiveInterfaceMapping1")); + } + + @Test + public void testTransitiveInterfaceMapping2(){ + PropertyConverterManager manager = new PropertyConverterManager(); + List<PropertyConverter<AutoCloseable>> converters = manager.getPropertyConverters(TypeLiteral.of(AutoCloseable.class)); + assertThat(converters, hasSize(1)); + + PropertyConverter<AutoCloseable> converter = converters.get(0); + AutoCloseable result = converter.convert("testTransitiveInterfaceMapping2"); + + assertThat(result, notNullValue()); + assertThat(result, instanceOf(C.class)); + assertThat(((C)result).getInValue(), equalTo("testTransitiveInterfaceMapping2")); + } + } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/4d560984/java8/core/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertyConverter ---------------------------------------------------------------------- diff --git a/java8/core/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertyConverter b/java8/core/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertyConverter new file mode 100644 index 0000000..d039696 --- /dev/null +++ b/java8/core/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertyConverter @@ -0,0 +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 current 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. +# +org.apache.tamaya.core.internal.CTestConverter \ No newline at end of file