From: Robert Varga Date: Sun, 3 Aug 2014 10:36:24 +0000 (+0000) Subject: Merge "Updated creation of Binding Spec Context to provide additional information." X-Git-Tag: release/helium~252 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=e609ad90a443547fa613118274a8773fa90c839f;hp=e9c5ba7ad7b6e976d9235035c9ae10e3fc81406e;p=yangtools.git Merge "Updated creation of Binding Spec Context to provide additional information." --- diff --git a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/TransformerGenerator.xtend b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/TransformerGenerator.xtend index ab6b734c0b..769a9e5bb7 100644 --- a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/TransformerGenerator.xtend +++ b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/TransformerGenerator.xtend @@ -29,7 +29,6 @@ import javassist.CtMethod import org.opendaylight.yangtools.binding.generator.util.BindingGeneratorUtil import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl import org.opendaylight.yangtools.binding.generator.util.Types -import org.opendaylight.yangtools.sal.binding.generator.util.ClassLoaderUtils import org.opendaylight.yangtools.sal.binding.generator.util.CodeGenerationException import org.opendaylight.yangtools.sal.binding.generator.util.XtendHelper import org.opendaylight.yangtools.sal.binding.model.api.Enumeration @@ -39,6 +38,7 @@ import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType import org.opendaylight.yangtools.sal.binding.model.api.ParameterizedType import org.opendaylight.yangtools.sal.binding.model.api.Type import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedTypeBuilder +import org.opendaylight.yangtools.util.ClassLoaderUtils import org.opendaylight.yangtools.yang.binding.Augmentation import org.opendaylight.yangtools.yang.binding.BindingCodec import org.opendaylight.yangtools.yang.binding.BindingDeserializer diff --git a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/ClassLoaderUtils.java b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/ClassLoaderUtils.java deleted file mode 100644 index 01ed2f0240..0000000000 --- a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/ClassLoaderUtils.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.yangtools.sal.binding.generator.util; - -import static com.google.common.base.Preconditions.checkNotNull; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.locks.Lock; - -import com.google.common.base.Joiner; -import com.google.common.base.Optional; - -/** - * @deprecated Use {@link org.opendaylight.yangtools.yang.binding.util.ClassLoaderUtils} instead. - */ -@Deprecated -public final class ClassLoaderUtils { - - private ClassLoaderUtils() { - throw new UnsupportedOperationException("Utility class"); - } - - public static V withClassLoader(final ClassLoader cls, final Callable function) throws Exception { - checkNotNull(cls, "Classloader should not be null"); - checkNotNull(function, "Function should not be null"); - - final ClassLoader oldCls = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader(cls); - return function.call(); - } finally { - Thread.currentThread().setContextClassLoader(oldCls); - } - } - - public static V withClassLoaderAndLock(final ClassLoader cls, final Lock lock, final Callable function) throws Exception { - checkNotNull(lock, "Lock should not be null"); - - lock.lock(); - try { - return withClassLoader(cls, function); - } finally { - lock.unlock(); - } - } - - /** - * @deprecated Use one of the other utility methods. - */ - @Deprecated - public static V withClassLoaderAndLock(final ClassLoader cls, final Optional lock, final Callable function) throws Exception { - if (lock.isPresent()) { - return withClassLoaderAndLock(cls, lock.get(), function); - } else { - return withClassLoader(cls, function); - } - } - - public static Object construct(final Constructor constructor, final List objects) - throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { - Object[] initargs = objects.toArray(new Object[] {}); - return constructor.newInstance(initargs); - } - - - public static Class loadClass(final ClassLoader cls, final String name) throws ClassNotFoundException { - if ("byte[]".equals(name)) { - return byte[].class; - } else if("char[]".equals(name)) { - return char[].class; - } - try { - return cls.loadClass(name); - } catch (ClassNotFoundException e) { - String[] components = name.split("\\."); - String potentialOuter; - int length = components.length; - if (length > 2 && (potentialOuter = components[length - 2]) != null && Character.isUpperCase(potentialOuter.charAt(0))) { - - String outerName = Joiner.on(".").join(Arrays.asList(components).subList(0, length - 1)); - String innerName = outerName + "$" + components[length-1]; - return cls.loadClass(innerName); - } else { - throw e; - } - } - } - - public static Class loadClassWithTCCL(final String name) throws ClassNotFoundException { - return loadClass(Thread.currentThread().getContextClassLoader(), name); - } - - public static Class tryToLoadClassWithTCCL(final String fullyQualifiedName) { - try { - return loadClassWithTCCL(fullyQualifiedName); - } catch (ClassNotFoundException e) { - return null; - } - } -} diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/BaseTemplate.xtend b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/BaseTemplate.xtend index 24d0a7fd60..eb7b948c54 100644 --- a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/BaseTemplate.xtend +++ b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/BaseTemplate.xtend @@ -498,15 +498,15 @@ abstract class BaseTemplate { «val numberClass = restrictions.lengthConstraints.iterator.next.min.class» public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() { «IF numberClass.equals(typeof(BigDecimal))» - «lengthMethodBody(restrictions, numberClass, className, varName)» + «lengthBody(restrictions, numberClass, className, varName)» «ELSE» - «lengthMethodBody(restrictions, typeof(BigInteger), className, varName)» + «lengthBody(restrictions, typeof(BigInteger), className, varName)» «ENDIF» } «ENDIF» ''' - def private lengthMethodBody(Restrictions restrictions, Class numberClass, String className, String varName) ''' + def private lengthBody(Restrictions restrictions, Class numberClass, String className, String varName) ''' if («varName» == null) { synchronized («className».class) { if («varName» == null) { @@ -526,9 +526,9 @@ abstract class BaseTemplate { «val number = returnType.importedNumber» public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() { «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)» - «rangeMethodBody(restrictions, BigDecimal, className, varName)» + «rangeBody(restrictions, BigDecimal, className, varName)» «ELSE» - «rangeMethodBody(restrictions, BigInteger, className, varName)» + «rangeBody(restrictions, BigInteger, className, varName)» «ENDIF» } «ENDIF» @@ -539,15 +539,15 @@ abstract class BaseTemplate { «val returnType = properties.iterator.next.returnType» public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() { «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)» - «rangeMethodBody(restrictions, BigDecimal, className, varName)» + «rangeBody(restrictions, BigDecimal, className, varName)» «ELSE» - «rangeMethodBody(restrictions, BigInteger, className, varName)» + «rangeBody(restrictions, BigInteger, className, varName)» «ENDIF» } «ENDIF» ''' - def private rangeMethodBody(Restrictions restrictions, Class numberClass, String className, String varName) ''' + def private rangeBody(Restrictions restrictions, Class numberClass, String className, String varName) ''' if («varName» == null) { synchronized («className».class) { if («varName» == null) { @@ -576,7 +576,7 @@ abstract class BaseTemplate { return BigInteger.importedName } - def private String numericValue(Class clazz, Object numberValue) { + def protected String numericValue(Class clazz, Object numberValue) { val number = clazz.importedName; val value = numberValue.toString if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) { diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/ClassTemplate.xtend b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/ClassTemplate.xtend index e92f84e489..afebd5846b 100644 --- a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/ClassTemplate.xtend +++ b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/ClassTemplate.xtend @@ -7,24 +7,28 @@ */ package org.opendaylight.yangtools.sal.java.api.generator +import com.google.common.collect.ImmutableList +import com.google.common.collect.Lists +import com.google.common.collect.Range +import com.google.common.io.BaseEncoding +import java.beans.ConstructorProperties +import java.math.BigDecimal +import java.math.BigInteger +import java.util.ArrayList +import java.util.Arrays +import java.util.Collections import java.util.List +import java.util.regex.Pattern import org.opendaylight.yangtools.binding.generator.util.TypeConstants import org.opendaylight.yangtools.sal.binding.model.api.Constant import org.opendaylight.yangtools.sal.binding.model.api.Enumeration import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType -import java.util.ArrayList -import java.util.Collections import java.util.Arrays import org.opendaylight.yangtools.sal.binding.model.api.Restrictions -import com.google.common.collect.Range -import java.util.regex.Pattern -import com.google.common.io.BaseEncoding -import java.beans.ConstructorProperties -import com.google.common.collect.Lists /** - * Template for generating JAVA class. + * Template for generating JAVA class. */ class ClassTemplate extends BaseTemplate { @@ -33,23 +37,22 @@ class ClassTemplate extends BaseTemplate { protected val List parentProperties protected val Iterable allProperties; protected val Restrictions restrictions - + /** * List of enumeration which are generated as JAVA enum type. */ protected val List enums - + /** * List of constant instances which are generated as JAVA public static final attributes. */ protected val List consts - + /** * List of generated types which are enclosed inside genType */ protected val List enclosedGeneratedTypes; - protected val GeneratedTransferObject genTO; /** @@ -78,7 +81,6 @@ class ClassTemplate extends BaseTemplate { this.enclosedGeneratedTypes = genType.enclosedTypes } - /** * Generates JAVA class source code (class body only). * @@ -88,7 +90,6 @@ class ClassTemplate extends BaseTemplate { return generateBody(true) } - override protected body() { generateBody(false); } @@ -107,9 +108,14 @@ class ClassTemplate extends BaseTemplate { «enumDeclarations» «constantsDeclarations» «generateFields» - - «constructors» + «IF restrictions != null && (!restrictions.rangeConstraints.nullOrEmpty || + !restrictions.lengthConstraints.nullOrEmpty)» + «generateConstraints» + + «ENDIF» + «constructors» + «defaultInstance» «FOR field : properties SEPARATOR "\n"» @@ -125,13 +131,30 @@ class ClassTemplate extends BaseTemplate { «generateToString(genTO.toStringIdentifiers)» - «generateLengthMethod("length", genTO, genTO.importedName, "_length")» + «generateLengthMethod("length", "_length")» - «generateRangeMethod("range", genTO.restrictions, genTO.importedName, "_range", allProperties)» + «generateRangeMethod("range", "_range")» } ''' + def private generateLengthMethod(String methodName, String varName) ''' + «IF restrictions != null && !(restrictions.lengthConstraints.empty)» + «val numberClass = restrictions.lengthConstraints.iterator.next.min.class» + public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() { + return «varName»; + } + «ENDIF» + ''' + + def private generateRangeMethod(String methodName, String varName) ''' + «IF restrictions != null && !(restrictions.rangeConstraints.empty)» + «val returnType = allProperties.iterator.next.returnType» + public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() { + return «varName»; + } + «ENDIF» + ''' /** * Template method which generates inner classes inside this interface. @@ -144,13 +167,12 @@ class ClassTemplate extends BaseTemplate { «IF (innerClass instanceof GeneratedTransferObject)» «val classTemplate = new ClassTemplate(innerClass as GeneratedTransferObject)» «classTemplate.generateAsInnerClass» - + «ENDIF» «ENDFOR» «ENDIF» ''' - - + def protected constructors() ''' «IF genTO.unionType» «genUnionConstructor» @@ -165,6 +187,55 @@ class ClassTemplate extends BaseTemplate { «ENDIF» ''' + def private generateConstraints() ''' + static { + «IF !restrictions.rangeConstraints.nullOrEmpty» + «generateRangeConstraints» + «ENDIF» + «IF !restrictions.lengthConstraints.nullOrEmpty» + «generateLengthConstraints» + «ENDIF» + } + ''' + + private def generateRangeConstraints() ''' + «IF !allProperties.nullOrEmpty» + «val returnType = allProperties.iterator.next.returnType» + «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)» + «rangeBody(restrictions, BigDecimal, genTO.importedName, "_range")» + «ELSE» + «rangeBody(restrictions, BigInteger, genTO.importedName, "_range")» + «ENDIF» + «ENDIF» + ''' + + private def rangeBody(Restrictions restrictions, Class numberClass, String className, String varName) ''' + «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder(); + «FOR r : restrictions.rangeConstraints» + builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»)); + «ENDFOR» + «varName» = builder.build(); + ''' + + private def lengthBody(Restrictions restrictions, Class numberClass, String className, String varName) ''' + «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder(); + «FOR r : restrictions.lengthConstraints» + builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»)); + «ENDFOR» + «varName» = builder.build(); + ''' + + private def generateLengthConstraints() ''' + «IF restrictions != null && !(restrictions.lengthConstraints.empty)» + «val numberClass = restrictions.lengthConstraints.iterator.next.min.class» + «IF numberClass.equals(typeof(BigDecimal))» + «lengthBody(restrictions, numberClass, genTO.importedName, "_length")» + «ELSE» + «lengthBody(restrictions, typeof(BigInteger), genTO.importedName, "_length")» + «ENDIF» + «ENDIF» + ''' + def protected allValuesConstructor() ''' «IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")» @«ConstructorProperties.importedName»("value") @@ -180,6 +251,7 @@ class ClassTemplate extends BaseTemplate { this.«p.fieldName» = «p.fieldName»; «ENDFOR» } + ''' def protected genUnionConstructor() ''' @@ -296,7 +368,7 @@ class ClassTemplate extends BaseTemplate { ENDFOR»« ENDIF »''' - + /** * Template method which generates JAVA enum type. * @@ -369,10 +441,10 @@ class ClassTemplate extends BaseTemplate { «val prop = getPropByName("value")» «IF prop != null» «IF !(restrictions.lengthConstraints.empty)» - private static «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _length; + private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _length; «ENDIF» «IF !(restrictions.rangeConstraints.empty)» - private static «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _range; + private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _range; «ENDIF» «ENDIF» «ENDIF» diff --git a/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/util/ClassLoaderUtils.java b/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/util/ClassLoaderUtils.java deleted file mode 100644 index e898e72479..0000000000 --- a/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/util/ClassLoaderUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.yangtools.concepts.util; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.concurrent.Callable; -import java.util.concurrent.locks.Lock; - -/** - * @deprecated Use {@link org.opendaylight.yangtools.yang.binding.util.ClassLoaderUtils} instead. - */ -@Deprecated -public final class ClassLoaderUtils { - - private ClassLoaderUtils() { - throw new UnsupportedOperationException("Utility class"); - } - - public static V withClassLoader(final ClassLoader cls, final Callable function) throws Exception { - return withClassLoaderAndLock(cls, null, function); - } - - public static V withClassLoaderAndLock(final ClassLoader cls, final Lock lock, final Callable function) throws Exception { - if (cls == null) { - throw new IllegalArgumentException("Classloader should not be null"); - } - if (function == null) { - throw new IllegalArgumentException("Function should not be null"); - } - - if (lock != null) { - lock.lock(); - } - ClassLoader oldCls = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader(cls); - return function.call(); - } finally { - Thread.currentThread().setContextClassLoader(oldCls); - if (lock != null) { - lock.unlock(); - } - } - } - - public static ParameterizedType findParameterizedType(final Class subclass, final Class genericType) { - if(subclass == null || genericType == null) { - throw new IllegalArgumentException("Class was not specified."); - } - for (Type type : subclass.getGenericInterfaces()) { - if (type instanceof ParameterizedType && genericType.equals(((ParameterizedType) type).getRawType())) { - return (ParameterizedType) type; - } - } - return null; - } - - public static Class

findFirstGenericArgument(final Class scannedClass, final Class genericType) { - try { - return withClassLoader(scannedClass.getClassLoader(), ClassLoaderUtils.findFirstGenericArgumentTask(scannedClass, genericType)); - } catch (Exception e) { - return null; - } - } - - private static Callable> findFirstGenericArgumentTask(final Class scannedClass, final Class genericType) { - return new Callable>() { - @Override - @SuppressWarnings("unchecked") - public Class

call() throws Exception { - final ParameterizedType augmentationGeneric = findParameterizedType(scannedClass, - genericType); - if (augmentationGeneric == null) { - return null; - } - return (Class

) augmentationGeneric.getActualTypeArguments()[0]; - } - }; - } - - public static Type getFirstGenericParameter(final Type type) { - if(type instanceof ParameterizedType) { - return ((ParameterizedType) type).getActualTypeArguments()[0]; - } - return null; - } - -} \ No newline at end of file diff --git a/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/util/ListenerRegistry.java b/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/util/ListenerRegistry.java index e8f271c075..07cc5ae6be 100644 --- a/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/util/ListenerRegistry.java +++ b/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/util/ListenerRegistry.java @@ -12,10 +12,16 @@ import java.util.Collections; import java.util.EventListener; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; + import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; import org.opendaylight.yangtools.concepts.ListenerRegistration; - +/** + * @deprecated Use {@link org.opendaylight.yangtools.util.ListenerRegistry} instead. + * + * @param + */ +@Deprecated public class ListenerRegistry implements Iterable> { private final ConcurrentHashMap,ListenerRegistration> listeners; @@ -34,7 +40,7 @@ public class ListenerRegistry implements Iterable register(T listener) { + public ListenerRegistration register(final T listener) { if (listener == null) { throw new IllegalArgumentException("Listener should not be null."); } @@ -42,28 +48,26 @@ public class ListenerRegistry implements Iterable ListenerRegistration registerWithType(L listener) { + + public ListenerRegistration registerWithType(final L listener) { ListenerRegistrationImpl ret = new ListenerRegistrationImpl(listener); listeners.put(ret,ret); return ret; } - + @Override public java.util.Iterator> iterator() { return unmodifiableView.iterator(); } @SuppressWarnings("rawtypes") - private void remove(ListenerRegistrationImpl registration) { + private void remove(final ListenerRegistrationImpl registration) { listeners.remove(registration); } - private class ListenerRegistrationImpl

// - extends AbstractObjectRegistration

// - implements ListenerRegistration

{ + private class ListenerRegistrationImpl

extends AbstractObjectRegistration

implements ListenerRegistration

{ - public ListenerRegistrationImpl(P instance) { + public ListenerRegistrationImpl(final P instance) { super(instance); } diff --git a/common/features/pom.xml b/common/features/pom.xml index 684d959f39..00c1cf4e88 100644 --- a/common/features/pom.xml +++ b/common/features/pom.xml @@ -171,7 +171,7 @@ org.opendaylight.yangtools - yang-data-json + yang-data-composite-node org.opendaylight.yangtools diff --git a/common/parent/pom.xml b/common/parent/pom.xml index 84d4dfe9da..7c62febf18 100644 --- a/common/parent/pom.xml +++ b/common/parent/pom.xml @@ -321,7 +321,7 @@ org.opendaylight.yangtools - yang-data-json + yang-data-composite-node 0.6.2-SNAPSHOT diff --git a/common/util/pom.xml b/common/util/pom.xml index d0ab7fc85e..5d185efe51 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -24,7 +24,10 @@ org.opendaylight.yangtools concepts - + + com.google.code.findbugs + jsr305 + org.slf4j slf4j-api @@ -43,6 +46,12 @@ junit test + + org.opendaylight.yangtools + mockito-configuration + test + 0.6.2-SNAPSHOT + diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/ClassLoaderUtils.java b/common/util/src/main/java/org/opendaylight/yangtools/util/ClassLoaderUtils.java new file mode 100644 index 0000000000..6f84ef81ff --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/ClassLoaderUtils.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Callable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ClassLoaderUtils { + private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderUtils.class); + + private ClassLoaderUtils() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * + * Runs {@link Supplier} with provided {@link ClassLoader}. + * + * Invokes supplies function and makes sure that original {@link ClassLoader} + * is context {@link ClassLoader} after execution. + * + * @param cls {@link ClassLoader} to be used. + * @param function Function to be executed. + * @return Result of supplier invocation. + * + */ + public static V withClassLoader(final ClassLoader cls, final Supplier function) { + checkNotNull(cls, "Classloader should not be null"); + checkNotNull(function, "Function should not be null"); + + final ClassLoader oldCls = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(cls); + return function.get(); + } finally { + Thread.currentThread().setContextClassLoader(oldCls); + } + } + + /** + * + * Runs {@link Callable} with provided {@link ClassLoader}. + * + * Invokes supplies function and makes sure that original {@link ClassLoader} + * is context {@link ClassLoader} after execution. + * + * @param cls {@link ClassLoader} to be used. + * @param function Function to be executed. + * @return Result of callable invocation. + * + */ + public static V withClassLoader(final ClassLoader cls, final Callable function) throws Exception { + checkNotNull(cls, "Classloader should not be null"); + checkNotNull(function, "Function should not be null"); + + final ClassLoader oldCls = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(cls); + return function.call(); + } finally { + Thread.currentThread().setContextClassLoader(oldCls); + } + } + + public static Object construct(final Constructor constructor, final List objects) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + final Object[] initargs = objects.toArray(); + return constructor.newInstance(initargs); + } + + /** + * + * Loads class using this supplied classloader. + * + * + * @param cls + * @param name String name of class. + * @return + * @throws ClassNotFoundException + */ + public static Class loadClass(final ClassLoader cls, final String name) throws ClassNotFoundException { + if ("byte[]".equals(name)) { + return byte[].class; + } + if ("char[]".equals(name)) { + return char[].class; + } + + try { + return cls.loadClass(name); + } catch (ClassNotFoundException e) { + String[] components = name.split("\\."); + String potentialOuter; + int length = components.length; + if (length > 2 && (potentialOuter = components[length - 2]) != null && Character.isUpperCase(potentialOuter.charAt(0))) { + String outerName = Joiner.on(".").join(Arrays.asList(components).subList(0, length - 1)); + String innerName = outerName + "$" + components[length-1]; + return cls.loadClass(innerName); + } else { + throw e; + } + } + } + + public static Class loadClassWithTCCL(final String name) throws ClassNotFoundException { + return loadClass(Thread.currentThread().getContextClassLoader(), name); + } + + public static Class tryToLoadClassWithTCCL(final String fullyQualifiedName) { + try { + return loadClassWithTCCL(fullyQualifiedName); + } catch (ClassNotFoundException e) { + LOG.debug("Failed to load class {}", fullyQualifiedName, e); + return null; + } + } + + public static Class

findFirstGenericArgument(final Class scannedClass, final Class genericType) { + return withClassLoader(scannedClass.getClassLoader(), ClassLoaderUtils.findFirstGenericArgumentTask(scannedClass, genericType)); + } + + private static Supplier> findFirstGenericArgumentTask(final Class scannedClass, final Class genericType) { + return new Supplier>() { + @Override + @SuppressWarnings("unchecked") + public Class

get() { + final ParameterizedType augmentationGeneric = findParameterizedType(scannedClass, genericType); + if (augmentationGeneric != null) { + return (Class

) augmentationGeneric.getActualTypeArguments()[0]; + } + return null; + } + }; + } + + public static ParameterizedType findParameterizedType(final Class subclass, final Class genericType) { + Preconditions.checkNotNull(subclass); + Preconditions.checkNotNull(genericType); + + for (Type type : subclass.getGenericInterfaces()) { + if (type instanceof ParameterizedType && genericType.equals(((ParameterizedType) type).getRawType())) { + return (ParameterizedType) type; + } + } + + LOG.debug("Class {} does not declare interface {}", subclass, genericType); + return null; + } + + public static Type getFirstGenericParameter(final Type type) { + if (type instanceof ParameterizedType) { + return ((ParameterizedType) type).getActualTypeArguments()[0]; + } + return null; + } +} diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/ExecutorServiceUtil.java b/common/util/src/main/java/org/opendaylight/yangtools/util/ExecutorServiceUtil.java index 36f729fb8f..51a8d16f84 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/ExecutorServiceUtil.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/ExecutorServiceUtil.java @@ -12,6 +12,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,11 +26,15 @@ public final class ExecutorServiceUtil { private static final class WaitInQueueExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) { + if( executor.isShutdown() ) { + throw new RejectedExecutionException( "Executor has been shutdown." ); + } + try { executor.getQueue().put(r); } catch (InterruptedException e) { - LOG.debug("Intterupted while waiting for queue", e); - throw new RejectedExecutionException("Interrupted while waiting for queue", e); + LOG.debug("Interrupted while attempting to put to the queue", e); + throw new RejectedExecutionException("Interrupted while attempting to put to the queue", e); } } } @@ -42,7 +47,7 @@ public final class ExecutorServiceUtil { } /** - * Create a {@link BlockingQueue} which does not allow for non-blocking addition to the queue. + * Creates a {@link BlockingQueue} which does not allow for non-blocking addition to the queue. * This is useful with {@link #waitInQueueExecutionHandler()} to turn force a * {@link ThreadPoolExecutor} to create as many threads as it is configured to before starting * to fill the queue. @@ -50,7 +55,7 @@ public final class ExecutorServiceUtil { * @param delegate Backing blocking queue. * @return A new blocking queue backed by the delegate */ - public BlockingQueue offerFailingBlockingQueue(final BlockingQueue delegate) { + public static BlockingQueue offerFailingBlockingQueue(final BlockingQueue delegate) { return new ForwardingBlockingQueue() { @Override public boolean offer(final E o) { @@ -65,12 +70,31 @@ public final class ExecutorServiceUtil { } /** - * Return a {@link RejectedExecutionHandler} which blocks on the {@link ThreadPoolExecutor}'s + * Returns a {@link RejectedExecutionHandler} which blocks on the {@link ThreadPoolExecutor}'s * backing queue if a new thread cannot be spawned. * * @return A shared RejectedExecutionHandler instance. */ - public RejectedExecutionHandler waitInQueueExecutionHandler() { + public static RejectedExecutionHandler waitInQueueExecutionHandler() { return WAIT_IN_QUEUE_HANDLER; } + + /** + * Tries to shutdown the given executor gracefully by awaiting termination for the given + * timeout period. If the timeout elapses before termination, the executor is forcefully + * shutdown. + */ + public static void tryGracefulShutdown(final ExecutorService executor, long timeout, + TimeUnit unit ) { + + executor.shutdown(); + + try { + if(!executor.awaitTermination(timeout, unit)) { + executor.shutdownNow(); + } + } catch( InterruptedException e ) { + executor.shutdownNow(); + } + } } diff --git a/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/util/Immutables.java b/common/util/src/main/java/org/opendaylight/yangtools/util/Immutables.java similarity index 97% rename from common/concepts/src/main/java/org/opendaylight/yangtools/concepts/util/Immutables.java rename to common/util/src/main/java/org/opendaylight/yangtools/util/Immutables.java index cc2d0186d4..e10977b6de 100644 --- a/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/util/Immutables.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/Immutables.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.concepts.util; +package org.opendaylight.yangtools.util; import java.math.BigDecimal; import java.math.BigInteger; diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/ListenerRegistry.java b/common/util/src/main/java/org/opendaylight/yangtools/util/ListenerRegistry.java new file mode 100644 index 0000000000..e8b1a3dca9 --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/ListenerRegistry.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.util; + + +import java.util.Collections; +import java.util.EventListener; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; +import org.opendaylight.yangtools.concepts.ListenerRegistration; + + +public class ListenerRegistry implements Iterable> { + + private final ConcurrentHashMap,ListenerRegistration> listeners; + final Set> unmodifiableView; + + @SuppressWarnings("unchecked") + public ListenerRegistry() { + listeners = new ConcurrentHashMap<>(); + // This conversion is known to be safe. + @SuppressWarnings("rawtypes") + final Set rawSet = Collections.unmodifiableSet(listeners.keySet()); + unmodifiableView = rawSet; + } + + public Iterable> getListeners() { + return unmodifiableView; + } + + public ListenerRegistration register(T listener) { + if (listener == null) { + throw new IllegalArgumentException("Listener should not be null."); + } + ListenerRegistrationImpl ret = new ListenerRegistrationImpl(listener); + listeners.put(ret,ret); + return ret; + } + + public ListenerRegistration registerWithType(L listener) { + ListenerRegistrationImpl ret = new ListenerRegistrationImpl(listener); + listeners.put(ret,ret); + return ret; + } + + @Override + public java.util.Iterator> iterator() { + return unmodifiableView.iterator(); + } + + @SuppressWarnings("rawtypes") + private void remove(ListenerRegistrationImpl registration) { + listeners.remove(registration); + } + + private class ListenerRegistrationImpl

// + extends AbstractObjectRegistration

// + implements ListenerRegistration

{ + + public ListenerRegistrationImpl(P instance) { + super(instance); + } + + @Override + protected void removeRegistration() { + ListenerRegistry.this.remove(this); + } + } + + public static ListenerRegistry create() { + return new ListenerRegistry<>(); + } +} diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/PropertyUtils.java b/common/util/src/main/java/org/opendaylight/yangtools/util/PropertyUtils.java new file mode 100644 index 0000000000..cc8f94d182 --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/PropertyUtils.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.yangtools.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Strings; + +/** + * Provides utilities for system properties. + * + * @author Thomas Pantelis + */ +public final class PropertyUtils { + + private static final Logger LOG = LoggerFactory.getLogger(PropertyUtils.class); + + private PropertyUtils() { + } + + /** + * Obtains the given property from the System properties and returns as an int. If the property + * is not found the specified default value is returned. If the property value can't be parsed + * to an int, a warning is logged and the default value is returned. + * + * @param propName the name of the property to get + * @param defaultValue the default value + * @return the System property as an int or the defaultValue if not found. + */ + public static int getIntSystemProperty( String propName, int defaultValue ) { + int propValue = defaultValue; + String strValue = System.getProperty(propName); + if (!Strings.isNullOrEmpty(strValue) && !strValue.trim().isEmpty() ) { + try { + propValue = Integer.parseInt(strValue); + } catch (NumberFormatException e) { + LOG.warn("Cannot parse value {} for system property {}, using default {}", + strValue, propName, defaultValue); + } + } + + return propValue; + } +} diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/AsyncNotifyingListenableFutureTask.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/AsyncNotifyingListenableFutureTask.java new file mode 100644 index 0000000000..69c94f32a3 --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/AsyncNotifyingListenableFutureTask.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.yangtools.util.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.util.concurrent.ExecutionList; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * A {@link FutureTask} that also implements the {@link ListenableFuture} interface similar to + * guava's {@link ListenableFutureTask}. This class differs from ListenableFutureTask in that it + * allows an {@link Executor} to be specified on construction that is used to execute listener + * callback Runnables, registered via {@link #addListener}, asynchronously when this task completes. + * This is useful when you want to guarantee listener executions are off-loaded onto another thread + * to avoid blocking the thread that completed this task, as a common use case is to pass an + * executor that runs tasks in the same thread as the caller (ie MoreExecutors#sameThreadExecutor) + * to {@link #addListener}. + *

+ * Note: the Executor specified on construction does not replace the Executor specified in + * {@link #addListener}. The latter Executor is still used however, if it is detected that the + * listener Runnable would execute in the thread that completed this task, the listener + * is executed on Executor specified on construction. + * + * @author Thomas Pantelis + * + * @param the Future result value type + */ +public class AsyncNotifyingListenableFutureTask extends FutureTask implements ListenableFuture { + + private static final Logger LOG = LoggerFactory.getLogger( AsyncNotifyingListenableFutureTask.class ); + + /** + * ThreadLocal used to detect if the task completion thread is running the listeners. + */ + private static final ThreadLocal ON_TASK_COMPLETION_THREAD_TL = new ThreadLocal<>(); + + /** + * The execution list to hold our listeners. + */ + private final ExecutionList executionList = new ExecutionList(); + + /** + * The executor used to run listener callbacks. + */ + private final Executor listenerExecutor; + + private AsyncNotifyingListenableFutureTask( Callable callable, @Nullable Executor listenerExecutor ) { + super( callable ); + this.listenerExecutor = listenerExecutor; + } + + private AsyncNotifyingListenableFutureTask( Runnable runnable, @Nullable V result, + @Nullable Executor listenerExecutor ) { + super( runnable, result ); + this.listenerExecutor = listenerExecutor; + } + + /** + * Creates an {@code AsyncListenableFutureTask} that will upon running, execute the given + * {@code Callable}. + * + * @param callable the callable task + * @param listenerExecutor the executor used to run listener callbacks asynchronously. + * If null, no executor is used. + */ + public static AsyncNotifyingListenableFutureTask create( Callable callable, + @Nullable Executor listenerExecutor ) { + return new AsyncNotifyingListenableFutureTask( callable, listenerExecutor ); + } + + /** + * Creates a {@code AsyncListenableFutureTask} that will upon running, execute the + * given {@code Runnable}, and arrange that {@code get} will return the + * given result on successful completion. + * + * @param runnable the runnable task + * @param result the result to return on successful completion. + * @param listenerExecutor the executor used to run listener callbacks asynchronously. + * If null, no executor is used. + */ + public static AsyncNotifyingListenableFutureTask create( Runnable runnable, @Nullable V result, + @Nullable Executor listenerExecutor ) { + return new AsyncNotifyingListenableFutureTask( runnable, result, listenerExecutor ); + } + + @Override + public void addListener( Runnable listener, Executor executor ) { + // If a listenerExecutor was specified on construction, wrap the listener Runnable in a + // DelegatingRunnable. If the specified executor is one that runs tasks in the same thread + // as the caller submitting the task (eg MoreExecutors#sameThreadExecutor) and the + // listener is executed from the #done method, then the DelegatingRunnable will detect this + // via the ThreadLocal and submit the listener Runnable to the listenerExecutor. + // + // On the other hand, if this task is already complete, the call to ExecutionList#add below + // will execute the listener Runnable immediately and, since the ThreadLocal won't be set, + // the DelegatingRunnable will run the listener Runnable inline. + + executionList.add( listenerExecutor == null ? listener : + new DelegatingRunnable( listener, listenerExecutor ), executor ); + } + + /** + * Called by the base class when the future result is set. We invoke our listeners. + */ + @Override + protected void done() { + ON_TASK_COMPLETION_THREAD_TL.set( Boolean.TRUE ); + try { + executionList.execute(); + } finally { + ON_TASK_COMPLETION_THREAD_TL.remove(); + } + } + + private static class DelegatingRunnable implements Runnable { + + private final Runnable delegate; + private final Executor executor; + + DelegatingRunnable( Runnable delegate, Executor executor ) { + this.delegate = delegate; + this.executor = executor; + } + + @Override + public void run() { + if( ON_TASK_COMPLETION_THREAD_TL.get() == null ) { + // We're not running on the task completion thread so run the delegate inline. + LOG.trace( "Executing ListenenableFuture Runnable on this thread: {}", + Thread.currentThread().getName() ); + delegate.run(); + } else { + // We're running on the task completion thread so off-load to the executor. + LOG.trace( "Submitting ListenenableFuture Runnable to the listenerExecutor", + Thread.currentThread().getName() ); + executor.execute( delegate ); + } + } + } +} diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/AsyncNotifyingListeningExecutorService.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/AsyncNotifyingListeningExecutorService.java new file mode 100644 index 0000000000..ef4670d9c5 --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/AsyncNotifyingListeningExecutorService.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.yangtools.util.concurrent; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.AbstractListeningExecutorService; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * An {@link ListeningExecutorService} implementation that also allows for an {@link Executor} to be + * specified on construction that is used to execute {@link ListenableFuture} callback Runnables, + * registered via {@link Futures#addCallback} or {@link ListenableFuture#addListener} directly, + * asynchronously when a task that is run on this executor completes. This is useful when you want + * to guarantee listener callback executions are off-loaded onto another thread to avoid blocking + * the thread that completed the task, as a common use case is to pass an executor that runs tasks + * in the same thread as the caller (ie MoreExecutors#sameThreadExecutor}) to + * {@link ListenableFuture#addListener}. + *

+ * Most commonly, this class would be used in lieu of MoreExecutors#listeningDecorator + * when the underlying delegate Executor is single-threaded, in which case, you may not want + * ListenableFuture callbacks to block the single thread. + *

+ * Note: the Executor specified on construction does not replace the Executor specified in + * {@link ListenableFuture#addListener}. The latter Executor is still used however, if it is + * detected that the listener Runnable would execute in the thread that completed the task, the + * listener is executed on Executor specified on construction. + * + * @author Thomas Pantelis + * @see AsyncNotifyingListenableFutureTask + */ +public class AsyncNotifyingListeningExecutorService extends AbstractListeningExecutorService { + + private final ExecutorService delegate; + private final Executor listenableFutureExecutor; + + /** + * Constructor. + * + * @param delegate the back-end ExecutorService. + * @param listenableFutureExecutor the executor used to run listener callbacks asynchronously. + * If null, no executor is used. + */ + public AsyncNotifyingListeningExecutorService( ExecutorService delegate, + @Nullable Executor listenableFutureExecutor ) { + this.delegate = Preconditions.checkNotNull( delegate ); + this.listenableFutureExecutor = listenableFutureExecutor; + } + + /** + * Creates an {@link AsyncNotifyingListenableFutureTask} instance with the listener Executor. + * + * @param task the Callable to execute + */ + private AsyncNotifyingListenableFutureTask newFutureTask( Callable task ) { + return AsyncNotifyingListenableFutureTask.create( task, listenableFutureExecutor ); + } + + /** + * Creates an {@link AsyncNotifyingListenableFutureTask} instance with the listener Executor. + * + * @param task the Runnable to execute + */ + private AsyncNotifyingListenableFutureTask newFutureTask( Runnable task, T result ) { + return AsyncNotifyingListenableFutureTask.create( task, result, listenableFutureExecutor ); + } + + /** + * Returns the delegate ExecutorService. + */ + protected ExecutorService getDelegate() { + return delegate; + } + + @Override + public boolean awaitTermination( long timeout, TimeUnit unit ) throws InterruptedException { + return delegate.awaitTermination( timeout, unit ); + } + + @Override + public boolean isShutdown() { + return delegate.isShutdown(); + } + + @Override + public boolean isTerminated() { + return delegate.isTerminated(); + } + + @Override + public void shutdown() { + delegate.shutdown(); + } + + @Override + public List shutdownNow() { + return delegate.shutdownNow(); + } + + @Override + public void execute( Runnable command ) { + delegate.execute( command ); + } + + @Override + public ListenableFuture submit( Callable task ) { + AsyncNotifyingListenableFutureTask futureTask = newFutureTask( task ); + delegate.execute( futureTask ); + return futureTask; + } + + @Override + public ListenableFuture submit( Runnable task ) { + AsyncNotifyingListenableFutureTask futureTask = newFutureTask( task, null ); + delegate.execute( futureTask ); + return futureTask; + } + + @Override + public ListenableFuture submit( Runnable task, T result ) { + AsyncNotifyingListenableFutureTask futureTask = newFutureTask( task, result ); + delegate.execute( futureTask ); + return futureTask; + } + + protected ToStringHelper addToStringAttributes( ToStringHelper toStringHelper ) { + return toStringHelper; + } + + @Override + public final String toString(){ + return addToStringAttributes( Objects.toStringHelper( this ) + .add( "delegate", delegate ) ).toString(); + } +} diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/CachedThreadPoolExecutor.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/CachedThreadPoolExecutor.java new file mode 100644 index 0000000000..4936efaba1 --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/CachedThreadPoolExecutor.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.util.concurrent; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +/** + * A ThreadPoolExecutor with a specified bounded queue capacity that favors reusing previously + * constructed threads, when they are available, over creating new threads. + *

+ * See {@link SpecialExecutors#newBoundedCachedThreadPool} for more details. + * + * @author Thomas Pantelis + */ +public class CachedThreadPoolExecutor extends ThreadPoolExecutor { + + private static final long IDLE_TIMEOUT_IN_SEC = 60L; + + private final AtomicLong largestBackingQueueSize = new AtomicLong( 0 ); + + private final ExecutorQueue executorQueue; + + private final String threadPrefix; + + private final int maximumQueueSize; + + private final RejectedTaskHandler rejectedTaskHandler; + + /** + * Constructs an instance. + * + * @param maximumPoolSize + * the maximum number of threads to allow in the pool. Threads will terminate after + * being idle for 60 seconds. + * @param maximumQueueSize + * the capacity of the queue. + * @param threadPrefix + * the name prefix for threads created by this executor. + */ + public CachedThreadPoolExecutor( int maximumPoolSize, int maximumQueueSize, String threadPrefix ) { + // We're using a custom SynchronousQueue that has a backing bounded LinkedBlockingQueue. + // We don't specify any core threads (first parameter) so, when a task is submitted, + // the base class will always try to offer to the queue. If there is an existing waiting + // thread, the offer will succeed and the task will be handed to the thread to execute. If + // there's no waiting thread, either because there are no threads in the pool or all threads + // are busy, the base class will try to create a new thread. If the maximum thread limit has + // been reached, the task will be rejected. We specify a RejectedTaskHandler that tries + // to offer to the backing queue. If that succeeds, the task will execute as soon as a + // thread becomes available. If the offer fails to the backing queue, the task is rejected. + super( 0, maximumPoolSize, IDLE_TIMEOUT_IN_SEC, TimeUnit.SECONDS, + new ExecutorQueue( maximumQueueSize ) ); + + this.threadPrefix = Preconditions.checkNotNull( threadPrefix ); + this.maximumQueueSize = maximumQueueSize; + + setThreadFactory( new ThreadFactoryBuilder().setDaemon( true ) + .setNameFormat( this.threadPrefix + "-%d" ).build() ); + + executorQueue = (ExecutorQueue)super.getQueue(); + + rejectedTaskHandler = new RejectedTaskHandler( + executorQueue.getBackingQueue(), largestBackingQueueSize ); + super.setRejectedExecutionHandler( rejectedTaskHandler ); + } + + @Override + public void setRejectedExecutionHandler( RejectedExecutionHandler handler ) { + rejectedTaskHandler.setDelegateRejectedExecutionHandler( handler ); + } + + @Override + public BlockingQueue getQueue(){ + return executorQueue.getBackingQueue(); + } + + public long getLargestQueueSize() { + return largestBackingQueueSize.get(); + } + + protected ToStringHelper addToStringAttributes( ToStringHelper toStringHelper ) { + return toStringHelper; + } + + @Override + public final String toString() { + return addToStringAttributes( Objects.toStringHelper( this ) + .add( "Thread Prefix", threadPrefix ) + .add( "Current Thread Pool Size", getPoolSize() ) + .add( "Largest Thread Pool Size", getLargestPoolSize() ) + .add( "Max Thread Pool Size", getMaximumPoolSize() ) + .add( "Current Queue Size", executorQueue.getBackingQueue().size() ) + .add( "Largest Queue Size", getLargestQueueSize() ) + .add( "Max Queue Size", maximumQueueSize ) + .add( "Active Thread Count", getActiveCount() ) + .add( "Completed Task Count", getCompletedTaskCount() ) + .add( "Total Task Count", getTaskCount() ) ).toString(); + } + + /** + * A customized SynchronousQueue that has a backing bounded LinkedBlockingQueue. This class + * overrides the #poll methods to first try to poll the backing queue for a task. If the backing + * queue is empty, it calls the base SynchronousQueue#poll method. In this manner, we get the + * thread reuse behavior of the SynchronousQueue with the added ability to queue tasks when all + * threads are busy. + */ + private static class ExecutorQueue extends SynchronousQueue { + + private static final long serialVersionUID = 1L; + + private static final long POLL_WAIT_TIME_IN_MS = 300; + + private final LinkedBlockingQueue backingQueue; + + ExecutorQueue( int maxBackingQueueSize ) { + backingQueue = new LinkedBlockingQueue<>( maxBackingQueueSize ); + } + + LinkedBlockingQueue getBackingQueue() { + return backingQueue; + } + + @Override + public Runnable poll( long timeout, TimeUnit unit ) throws InterruptedException { + long totalWaitTime = unit.toMillis( timeout ); + long waitTime = Math.min( totalWaitTime, POLL_WAIT_TIME_IN_MS ); + Runnable task = null; + + // We loop here, each time polling the backingQueue first then our queue, instead of + // polling each just once. This is to handle the following timing edge case: + // + // We poll the backingQueue and it's empty but, before the call to super.poll, + // a task is offered but no thread is immediately available and the task is put on the + // backingQueue. There is a slight chance that all the other threads could be at the + // same point, in which case they would all call super.poll and wait. If we only + // called poll once, no thread would execute the task (unless/until another task was + // later submitted). But by looping and breaking the specified timeout into small + // periods, one thread will eventually wake up and get the task from the backingQueue + // and execute it, although slightly delayed. + + while( task == null ) { + // First try to get a task from the backing queue. + task = backingQueue.poll(); + if( task == null ) { + // No task in backing - call the base class to wait for one to be offered. + task = super.poll( waitTime, TimeUnit.MILLISECONDS ); + + totalWaitTime -= POLL_WAIT_TIME_IN_MS; + if( totalWaitTime <= 0 ) { + break; + } + + waitTime = Math.min( totalWaitTime, POLL_WAIT_TIME_IN_MS ); + } + } + + return task; + } + + @Override + public Runnable poll() { + Runnable task = backingQueue.poll(); + return task != null ? task : super.poll(); + } + } + + /** + * Internal RejectedExecutionHandler that tries to offer rejected tasks to the backing queue. + * If the queue is full, we throw a RejectedExecutionException by default. The client can + * override this behavior be specifying their own RejectedExecutionHandler, in which case we + * delegate to that handler. + */ + private static class RejectedTaskHandler implements RejectedExecutionHandler { + + private final LinkedBlockingQueue backingQueue; + private final AtomicLong largestBackingQueueSize; + private volatile RejectedExecutionHandler delegateRejectedExecutionHandler; + + RejectedTaskHandler( LinkedBlockingQueue backingQueue, + AtomicLong largestBackingQueueSize ) { + this.backingQueue = backingQueue; + this.largestBackingQueueSize = largestBackingQueueSize; + } + + void setDelegateRejectedExecutionHandler( + RejectedExecutionHandler delegateRejectedExecutionHandler ){ + this.delegateRejectedExecutionHandler = delegateRejectedExecutionHandler; + } + + @Override + public void rejectedExecution( Runnable task, ThreadPoolExecutor executor ) { + if( executor.isShutdown() ) { + throw new RejectedExecutionException( "Executor has been shutdown." ); + } + + if( !backingQueue.offer( task ) ) { + if( delegateRejectedExecutionHandler != null ) { + delegateRejectedExecutionHandler.rejectedExecution( task, executor ); + } else { + throw new RejectedExecutionException( + "All threads are in use and the queue is full" ); + } + } + + largestBackingQueueSize.incrementAndGet(); + long size = backingQueue.size(); + long largest = largestBackingQueueSize.get(); + if( size > largest ) { + largestBackingQueueSize.compareAndSet( largest, size ); + } + } + } +} diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/DeadlockDetectingListeningExecutorService.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/DeadlockDetectingListeningExecutorService.java index 39332be85e..011872d6b1 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/DeadlockDetectingListeningExecutorService.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/DeadlockDetectingListeningExecutorService.java @@ -11,18 +11,17 @@ package org.opendaylight.yangtools.util.concurrent; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Function; -import com.google.common.util.concurrent.AbstractListeningExecutorService; import com.google.common.util.concurrent.ForwardingListenableFuture; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListenableFutureTask; - -import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.annotation.Nullable; + /** * An implementation of ListeningExecutorService that attempts to detect deadlock scenarios that * could occur if clients invoke the returned Future's get methods synchronously. @@ -45,10 +44,9 @@ import java.util.concurrent.TimeoutException; * * @author Thomas Pantelis */ -public class DeadlockDetectingListeningExecutorService extends AbstractListeningExecutorService { +public class DeadlockDetectingListeningExecutorService extends AsyncNotifyingListeningExecutorService { private final ThreadLocal deadlockDetector = new ThreadLocal<>(); private final Function deadlockExceptionFunction; - private final ExecutorService delegate; /** * Constructor. @@ -57,61 +55,45 @@ public class DeadlockDetectingListeningExecutorService extends AbstractListening * @param deadlockExceptionFunction Function that returns an Exception instance to set as the * cause of the ExecutionException when a deadlock is detected. */ - public DeadlockDetectingListeningExecutorService(final ExecutorService delegate, - final Function deadlockExceptionFunction) { - this.delegate = checkNotNull(delegate); - this.deadlockExceptionFunction = checkNotNull(deadlockExceptionFunction); - } - - @Override - public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException { - return delegate.awaitTermination(timeout, unit); - } - - @Override - public boolean isShutdown() { - return delegate.isShutdown(); - } - - @Override - public boolean isTerminated() { - return delegate.isTerminated(); + public DeadlockDetectingListeningExecutorService( ExecutorService delegate, + Function deadlockExceptionFunction ) { + this(delegate, deadlockExceptionFunction, null); } - @Override - public void shutdown() { - delegate.shutdown(); - } - - @Override - public List shutdownNow() { - return delegate.shutdownNow(); + /** + * Constructor. + * + * @param delegate the backing ExecutorService. + * @param deadlockExceptionFunction Function that returns an Exception instance to set as the + * cause of the ExecutionException when a deadlock is detected. + * @param listenableFutureExecutor the executor used to run listener callbacks asynchronously. + * If null, no executor is used. + */ + public DeadlockDetectingListeningExecutorService( ExecutorService delegate, + Function deadlockExceptionFunction, + @Nullable Executor listenableFutureExecutor ) { + super(delegate, listenableFutureExecutor); + this.deadlockExceptionFunction = checkNotNull(deadlockExceptionFunction); } @Override - public void execute(final Runnable command) { - delegate.execute(wrapRunnable(command)); + public void execute( Runnable command ){ + getDelegate().execute(wrapRunnable(command)); } @Override - public ListenableFuture submit(final Callable task ) { - final ListenableFutureTask futureTask = ListenableFutureTask.create(wrapCallable(task)); - delegate.execute(futureTask); - return wrapListenableFuture(futureTask); + public ListenableFuture submit( Callable task ){ + return wrapListenableFuture(super.submit(wrapCallable(task))); } @Override - public ListenableFuture submit( final Runnable task ) { - ListenableFutureTask futureTask = ListenableFutureTask.create(wrapRunnable(task), null); - delegate.execute(futureTask); - return wrapListenableFuture(futureTask); + public ListenableFuture submit( Runnable task ){ + return wrapListenableFuture(super.submit(wrapRunnable(task))); } @Override - public ListenableFuture submit(final Runnable task, final T result) { - ListenableFutureTask futureTask = ListenableFutureTask.create(wrapRunnable(task), result); - delegate.execute(futureTask); - return wrapListenableFuture(futureTask); + public ListenableFuture submit( Runnable task, T result ){ + return wrapListenableFuture(super.submit(wrapRunnable(task), result)); } private Runnable wrapRunnable(final Runnable task) { @@ -122,7 +104,7 @@ public class DeadlockDetectingListeningExecutorService extends AbstractListening try { task.run(); } finally { - deadlockDetector.set(null); + deadlockDetector.remove(); } } }; @@ -136,7 +118,7 @@ public class DeadlockDetectingListeningExecutorService extends AbstractListening try { return delagate.call(); } finally { - deadlockDetector.set(null); + deadlockDetector.remove(); } } }; @@ -144,11 +126,12 @@ public class DeadlockDetectingListeningExecutorService extends AbstractListening private ListenableFuture wrapListenableFuture(final ListenableFuture delegate ) { /* - * This creates a forwarding Future that overrides calls to get(...) to check, via the ThreadLocal, - * if the caller is doing a blocking call on a thread from this executor. If so, we detect this as - * a deadlock and throw an ExecutionException even though it may not be a deadlock if there are - * more than 1 thread in the pool. Either way, there's bad practice somewhere, either on the client - * side for doing a blocking call or in the framework's threading model. + * This creates a forwarding Future that overrides calls to get(...) to check, via the + * ThreadLocal, if the caller is doing a blocking call on a thread from this executor. If + * so, we detect this as a deadlock and throw an ExecutionException even though it may not + * be a deadlock if there are more than 1 thread in the pool. Either way, there's bad + * practice somewhere, either on the client side for doing a blocking call or in the + * framework's threading model. */ return new ForwardingListenableFuture.SimpleForwardingListenableFuture(delegate) { @Override diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/FastThreadPoolExecutor.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/FastThreadPoolExecutor.java new file mode 100644 index 0000000000..b7549eb24e --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/FastThreadPoolExecutor.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.yangtools.util.concurrent; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +/** + * A ThreadPoolExecutor with a specified bounded queue capacity that favors creating new threads + * over queuing, as the former is faster. + *

+ * See {@link SpecialExecutors#newFastBlockingThreadPool} for more details. + * + * @author Thomas Pantelis + */ +public class FastThreadPoolExecutor extends ThreadPoolExecutor { + + private static final long DEFAULT_IDLE_TIMEOUT_IN_SEC = 15L; + + private final String threadPrefix; + private final int maximumQueueSize; + + /** + * Constructs a FastThreadPoolExecutor instance. + * + * @param maximumPoolSize + * the maximum number of threads to allow in the pool. Threads will terminate after + * being idle for 15 seconds. + * @param maximumQueueSize + * the capacity of the queue. + * @param threadPrefix + * the name prefix for threads created by this executor. + */ + public FastThreadPoolExecutor( int maximumPoolSize, int maximumQueueSize, String threadPrefix ) { + this( maximumPoolSize, maximumQueueSize, DEFAULT_IDLE_TIMEOUT_IN_SEC, TimeUnit.SECONDS, + threadPrefix ); + } + + /** + * Constructs a FastThreadPoolExecutor instance. + * + * @param maximumPoolSize + * the maximum number of threads to allow in the pool. + * @param maximumQueueSize + * the capacity of the queue. + * @param keepAliveTime + * the maximum time that idle threads will wait for new tasks before terminating. + * @param unit + * the time unit for the keepAliveTime argument + * @param threadPrefix + * the name prefix for threads created by this executor. + */ + public FastThreadPoolExecutor( int maximumPoolSize, int maximumQueueSize, long keepAliveTime, + TimeUnit unit, String threadPrefix ) { + // We use all core threads (the first 2 parameters below equal) so, when a task is submitted, + // if the thread limit hasn't been reached, a new thread will be spawned to execute + // the task even if there is an existing idle thread in the pool. This is faster than + // handing the task to an existing idle thread via the queue. Once the thread limit is + // reached, subsequent tasks will be queued. If the queue is full, tasks will be rejected. + + super( maximumPoolSize, maximumPoolSize, keepAliveTime, unit, + new LinkedBlockingQueue( maximumQueueSize ) ); + + this.threadPrefix = threadPrefix; + this.maximumQueueSize = maximumQueueSize; + + setThreadFactory( new ThreadFactoryBuilder().setDaemon( true ) + .setNameFormat( threadPrefix + "-%d" ).build() ); + + if( keepAliveTime > 0 ) { + // Need to specifically configure core threads to timeout. + allowCoreThreadTimeOut( true ); + } + } + + protected ToStringHelper addToStringAttributes( ToStringHelper toStringHelper ) { + return toStringHelper; + } + + @Override + public final String toString() { + return addToStringAttributes( Objects.toStringHelper( this ) + .add( "Thread Prefix", threadPrefix ) + .add( "Current Thread Pool Size", getPoolSize() ) + .add( "Largest Thread Pool Size", getLargestPoolSize() ) + .add( "Max Thread Pool Size", getMaximumPoolSize() ) + .add( "Current Queue Size", getQueue().size() ) + .add( "Max Queue Size", maximumQueueSize ) + .add( "Active Thread Count", getActiveCount() ) + .add( "Completed Task Count", getCompletedTaskCount() ) + .add( "Total Task Count", getTaskCount() ) ).toString(); + } +} diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/NotificationManager.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/NotificationManager.java new file mode 100644 index 0000000000..41cc7dcc0e --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/NotificationManager.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.yangtools.util.concurrent; + +import java.util.concurrent.RejectedExecutionException; + +/** + * Interface for a class that manages queuing and dispatching notifications for multiple listeners. + * + * @author Thomas Pantelis + * + * @param the listener type + * @param the notification type + */ +public interface NotificationManager { + + /** + * Submits a notification to be queued and dispatched to the given listener. + *

+ * Note: This method may block if the listener queue is currently full. + * + * @param listener the listener to notify + * @param notification the notification to dispatch + * @throws RejectedExecutionException if the notification can't be queued for dispatching + */ + void submitNotification( L listener, N notification ) + throws RejectedExecutionException; + + /** + * Submits notifications to be queued and dispatched to the given listener. + *

+ * Note: This method may block if the listener queue is currently full. + * + * @param listener the listener to notify + * @param notifications the notifications to dispatch + * @throws RejectedExecutionException if a notification can't be queued for dispatching + */ + void submitNotifications( L listener, Iterable notifications ) + throws RejectedExecutionException; + +} \ No newline at end of file diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/QueuedNotificationManager.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/QueuedNotificationManager.java new file mode 100644 index 0000000000..472520d998 --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/QueuedNotificationManager.java @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.yangtools.util.concurrent; + +import java.util.Arrays; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import javax.annotation.concurrent.GuardedBy; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +/** + * This class manages queuing and dispatching notifications for multiple listeners concurrently. + * Notifications are queued on a per-listener basis and dispatched serially to each listener via an + * {@link Executor}. + *

+ * This class optimizes its memory footprint by only allocating and maintaining a queue and executor + * task for a listener when there are pending notifications. On the first notification(s), a queue + * is created and a task is submitted to the executor to dispatch the queue to the associated + * listener. Any subsequent notifications that occur before all previous notifications have been + * dispatched are appended to the existing queue. When all notifications have been dispatched, the + * queue and task are discarded. + * + * @author Thomas Pantelis + * + * @param the listener type + * @param the notification type + */ +public class QueuedNotificationManager implements NotificationManager { + + /** + * Interface implemented by clients that does the work of invoking listeners with notifications. + * + * @author Thomas Pantelis + * + * @param the listener type + * @param the notification type + */ + public interface Invoker { + + /** + * Called to invoke a listener with a notification. + * + * @param listener the listener to invoke + * @param notification the notification to send + */ + void invokeListener( L listener, N notification ); + } + + private static final Logger LOG = LoggerFactory.getLogger( QueuedNotificationManager.class ); + + private final Executor executor; + private final Invoker listenerInvoker; + + private final ConcurrentMap,NotificationTask> + listenerCache = new ConcurrentHashMap<>(); + + private final String name; + private final int maxQueueCapacity; + + /** + * Constructor. + * + * @param executor the {@link Executor} to use for notification tasks + * @param listenerInvoker the {@link Invoker} to use for invoking listeners + * @param maxQueueCapacity the capacity of each listener queue + * @param name the name of this instance for logging info + */ + public QueuedNotificationManager( Executor executor, Invoker listenerInvoker, + int maxQueueCapacity, String name ) { + this.executor = Preconditions.checkNotNull( executor ); + this.listenerInvoker = Preconditions.checkNotNull( listenerInvoker ); + Preconditions.checkArgument( maxQueueCapacity > 0, "maxQueueCapacity must be > 0 " ); + this.maxQueueCapacity = maxQueueCapacity; + this.name = Preconditions.checkNotNull( name ); + } + + /* (non-Javadoc) + * @see org.opendaylight.yangtools.util.concurrent.NotificationManager#addNotification(L, N) + */ + @Override + public void submitNotification( final L listener, final N notification ) + throws RejectedExecutionException { + + if( notification == null ) { + return; + } + + submitNotifications( listener, Arrays.asList( notification ) ); + } + + /* (non-Javadoc) + * @see org.opendaylight.yangtools.util.concurrent.NotificationManager#submitNotifications(L, java.util.Collection) + */ + @Override + public void submitNotifications( final L listener, final Iterable notifications ) + throws RejectedExecutionException { + + if( notifications == null || listener == null ) { + return; + } + + if( LOG.isTraceEnabled() ) { + LOG.trace( "{}: submitNotifications for listener {}: {}", + name, listener.getClass(), notifications ); + } + + ListenerKey key = new ListenerKey<>( listener ); + NotificationTask newNotificationTask = null; + + // Keep looping until we are either able to add a new NotificationTask or are able to + // add our notifications to an existing NotificationTask. Eventually one or the other + // will occur. + + try { + while( true ) { + NotificationTask existingTask = listenerCache.get( key ); + + if( existingTask == null || !existingTask.submitNotifications( notifications ) ) { + + // Either there's no existing task or we couldn't add our notifications to the + // existing one because it's in the process of exiting and removing itself from + // the cache. Either way try to put a new task in the cache. If we can't put + // then either the existing one is still there and hasn't removed itself quite + // yet or some other concurrent thread beat us to the put although this method + // shouldn't be called concurrently for the same listener as that would violate + // notification ordering. In any case loop back up and try again. + + if( newNotificationTask == null ) { + newNotificationTask = new NotificationTask( key, notifications ); + } + + existingTask = listenerCache.putIfAbsent( key, newNotificationTask ); + if( existingTask == null ) { + + // We were able to put our new task - now submit it to the executor and + // we're done. If it throws a RejectedxecutionException, let that propagate + // to the caller. + + LOG.debug( "{}: Submitting NotificationTask for listener {}", + name, listener.getClass() ); + + executor.execute( newNotificationTask ); + break; + } + } else { + + // We were able to add our notifications to an existing task so we're done. + + break; + } + } + } catch( InterruptedException e ) { + + // We were interrupted trying to offer to the listener's queue. Somebody's probably + // telling us to quit. + + LOG.debug( "{}: Interrupted trying to add to {} listener's queue", + name, listener.getClass() ); + } + + if( LOG.isTraceEnabled() ) { + LOG.trace( "{}: submitNotifications dine for listener {}", + name, listener.getClass() ); + } + } + + /** + * Used as the listenerCache map key. We key by listener reference identity hashCode/equals. + * Since we don't know anything about the listener class implementations and we're mixing + * multiple listener class instances in the same map, this avoids any potential issue with an + * equals implementation that just blindly casts the other Object to compare instead of checking + * for instanceof. + */ + private static class ListenerKey { + + private final L listener; + + public ListenerKey( L listener ) { + this.listener = listener; + } + + L getListener() { + return listener; + } + + @Override + public int hashCode() { + return System.identityHashCode( listener ); + } + + @Override + public boolean equals( Object obj ) { + ListenerKey other = (ListenerKey) obj; + return listener == other.listener; + } + } + + /** + * Executor task for a single listener that queues notifications and sends them serially to the + * listener. + */ + private class NotificationTask implements Runnable { + + private final BlockingQueue notificationQueue; + + private volatile boolean done = false; + + @GuardedBy("queuingLock") + private boolean queuedNotifications = false; + + private final Lock queuingLock = new ReentrantLock(); + + private final ListenerKey listenerKey; + + NotificationTask( ListenerKey listenerKey, Iterable notifications ) { + + this.listenerKey = listenerKey; + this.notificationQueue = new LinkedBlockingQueue<>( maxQueueCapacity ); + + for( N notification: notifications ) { + this.notificationQueue.add( notification ); + } + } + + boolean submitNotifications( Iterable notifications ) throws InterruptedException { + + queuingLock.lock(); + try { + + // Check the done flag - if true then #run is in the process of exiting so return + // false to indicate such. Otherwise, offer the notifications to the queue. + + if( done ) { + return false; + } + + for( N notification: notifications ) { + + while( true ) { + + // Try to offer for up to a minute and log a message if it times out. + + // FIXME: we loop forever to guarantee delivery however this leaves it open + // for 1 rogue listener to bring everyone to a halt. Another option is to + // limit the tries and give up after a while and drop the notification. + // Given a reasonably large queue capacity and long timeout, if we still + // can't queue then most likely the listener is an unrecoverable state + // (deadlock or endless loop). + + if( LOG.isDebugEnabled() ) { + LOG.debug( "{}: Offering notification to the queue for listener {}: {}", + name, listenerKey.getListener().getClass(), notification ); + } + + if( notificationQueue.offer( notification, 1, TimeUnit.MINUTES ) ) { + break; + } + + LOG.warn( + "{}: Timed out trying to offer a notification to the queue for listener {}." + + "The queue has reached its capacity of {}", + name, listenerKey.getListener().getClass(), maxQueueCapacity ); + } + } + + // Set the queuedNotifications flag to tell #run that we've just queued + // notifications and not to exit yet, even if it thinks the queue is empty at this + // point. + + queuedNotifications = true; + + } finally { + queuingLock.unlock(); + } + + return true; + } + + @Override + public void run() { + + try { + // Loop until we've dispatched all the notifications in the queue. + + while( true ) { + + // Get the notification at the head of the queue, waiting a little bit for one + // to get offered. + + N notification = notificationQueue.poll( 10, TimeUnit.MILLISECONDS ); + if( notification == null ) { + + // The queue is empty - try to get the queuingLock. If we can't get the lock + // then #submitNotifications is in the process of offering to the queue so + // we'll loop back up and poll the queue again. + + if( queuingLock.tryLock() ) { + try { + + // Check the queuedNotifications flag to see if #submitNotifications + // has offered new notification(s) to the queue. If so, loop back up + // and poll the queue again. Otherwise set done to true and exit. + // Once we set the done flag and unlock, calls to + // #submitNotifications will fail and a new task will be created. + + if( !queuedNotifications ) { + done = true; + break; + } + + // Clear the queuedNotifications flag so we'll try to exit the next + // time through the loop when the queue is empty. + + queuedNotifications = false; + + } finally { + queuingLock.unlock(); + } + } + } + + notifyListener( notification ); + } + } catch( InterruptedException e ) { + + // The executor is probably shutting down so log as debug. + LOG.debug( "{}: Interrupted trying to remove from {} listener's queue", + name, listenerKey.getListener().getClass() ); + } finally { + + // We're exiting, gracefully or not - either way make sure we always remove + // ourselves from the cache. + + listenerCache.remove( listenerKey ); + } + } + + private void notifyListener( N notification ) { + + if( notification == null ) { + return; + } + + try { + + if( LOG.isDebugEnabled() ) { + LOG.debug( "{}: Invoking listener {} with notification: {}", + name, listenerKey.getListener().getClass(), notification ); + } + + listenerInvoker.invokeListener( listenerKey.getListener(), notification ); + + } catch( RuntimeException e ) { + + // We'll let a RuntimeException from the listener slide and keep sending any + // remaining notifications. + + LOG.error( String.format( "%1$s: Error notifying listener %2$s", name, + listenerKey.getListener().getClass() ), e ); + + } catch( Error e ) { + + // A JVM Error is severe - best practice is to throw them up the chain. Set done to + // true so no new notifications can be added to this task as we're about to bail. + + done = true; + throw e; + } + } + } +} diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/SpecialExecutors.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/SpecialExecutors.java new file mode 100644 index 0000000000..0548d7a091 --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/SpecialExecutors.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.yangtools.util.concurrent; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Factory methods for creating {@link ExecutorService} instances with specific configurations. + + * @author Thomas Pantelis + */ +public final class SpecialExecutors { + + private SpecialExecutors() { + } + + /** + * Creates an ExecutorService with a specified bounded queue capacity that favors creating new + * threads over queuing, as the former is faster, so threads will only be reused when the thread + * limit is exceeded and tasks are queued. If the maximum queue capacity is reached, subsequent + * tasks will be rejected. + *

+ * For example, if the maximum number of threads is 100 and 100 short-lived tasks are submitted + * within say 10 seconds, then 100 threads will be created and used - previously constructed + * idle threads will not be reused. This provides the fastest execution of the 100 tasks at the + * expense of memory and thread resource overhead. Therefore it is advisable to specify a + * relatively small thread limit (probably no more than 50). + *

+ * Threads that have not been used for 15 seconds are terminated and removed from the pool. + * Thus, a pool that remains idle for long enough will not consume any resources. + *

+ * If you need an executor with less memory and thread resource overhead where slower execution + * time is acceptable, consider using {@link #newBoundedCachedThreadPool }. + * + * @param maximumPoolSize + * the maximum number of threads to allow in the pool. Threads will terminate after + * being idle for 15 seconds. + * @param maximumQueueSize + * the capacity of the queue. + * @param threadPrefix + * the name prefix for threads created by this executor. + * @return a new ExecutorService with the specified configuration. + */ + public static ExecutorService newBoundedFastThreadPool( int maximumPoolSize, + int maximumQueueSize, String threadPrefix ) { + return new FastThreadPoolExecutor( maximumPoolSize, maximumQueueSize, threadPrefix ); + } + + /** + * Creates an ExecutorService similar to {@link #newBoundedFastThreadPool } except that it + * handles rejected tasks by running them in the same thread as the caller. Therefore if the + * queue is full, the caller submitting the task will be blocked until the task completes. In + * this manner, tasks are never rejected. + * + * @param maximumPoolSize + * the maximum number of threads to allow in the pool. Threads will terminate after + * being idle for 15 seconds. + * @param maximumQueueSize + * the capacity of the queue. + * @param threadPrefix + * the name prefix for threads created by this executor. + * @return a new ExecutorService with the specified configuration. + */ + public static ExecutorService newBlockingBoundedFastThreadPool( int maximumPoolSize, + int maximumQueueSize, String threadPrefix ) { + + FastThreadPoolExecutor executor = + new FastThreadPoolExecutor( maximumPoolSize, maximumQueueSize, threadPrefix ); + executor.setRejectedExecutionHandler( new ThreadPoolExecutor.CallerRunsPolicy() ); + return executor; + } + + /** + * Creates an ExecutorService with a specified bounded queue capacity that favors reusing + * previously constructed threads, when they are available, over creating new threads. When a + * task is submitted, if no existing thread is available, a new thread will be created and added + * to the pool. If there is an existing idle thread available, the task will be handed to that + * thread to execute. If the specified maximum thread limit is reached, subsequent tasks will be + * queued and will execute as threads become available. If the maximum queue capacity is + * reached, subsequent tasks will be rejected. + *

+ * Threads that have not been used for sixty seconds are terminated and removed from the pool. + * Thus, a pool that remains idle for long enough will not consume any resources. + *

+ * By reusing threads when possible, this executor optimizes for reduced memory and thread + * resource overhead at the expense of execution time. + *

+ * If you need an executor with faster execution time where increased memory and thread resource + * overhead is acceptable, consider using {@link #newBoundedFastThreadPool }. + * + * @param maximumPoolSize + * the maximum number of threads to allow in the pool. Threads will terminate after + * being idle for 60 seconds. + * @param maximumQueueSize + * the capacity of the queue. + * @param threadPrefix + * the name prefix for threads created by this executor. + * @return a new ExecutorService with the specified configuration. + */ + public static ExecutorService newBoundedCachedThreadPool( int maximumPoolSize, + int maximumQueueSize, String threadPrefix ) { + return new CachedThreadPoolExecutor( maximumPoolSize, maximumQueueSize, threadPrefix ); + } + + /** + * Creates an ExecutorService similar to {@link #newBoundedCachedThreadPool } except that it + * handles rejected tasks by running them in the same thread as the caller. Therefore if the + * queue is full, the caller submitting the task will be blocked until the task completes. In + * this manner, tasks are never rejected. + * + * @param maximumPoolSize + * the maximum number of threads to allow in the pool. Threads will terminate after + * being idle for 60 seconds. + * @param maximumQueueSize + * the capacity of the queue. + * @param threadPrefix + * the name prefix for threads created by this executor. + * @return a new ExecutorService with the specified configuration. + */ + public static ExecutorService newBlockingBoundedCachedThreadPool( int maximumPoolSize, + int maximumQueueSize, String threadPrefix ) { + + CachedThreadPoolExecutor executor = + new CachedThreadPoolExecutor( maximumPoolSize, maximumQueueSize, threadPrefix ); + executor.setRejectedExecutionHandler( new ThreadPoolExecutor.CallerRunsPolicy() ); + return executor; + } + + /** + * Creates an ExecutorService that uses a single worker thread operating off a bounded queue + * with the specified capacity. Tasks are guaranteed to execute sequentially, and no more than + * one task will be active at any given time. If the maximum queue capacity is reached, + * subsequent tasks will be rejected. + * + * @param maximumQueueSize + * the capacity of the queue. + * @param threadPrefix + * the name prefix for the thread created by this executor. + * @return a new ExecutorService with the specified configuration. + */ + public static ExecutorService newBoundedSingleThreadExecutor( int maximumQueueSize, + String threadPrefix ) { + return new FastThreadPoolExecutor( 1, maximumQueueSize, Long.MAX_VALUE, TimeUnit.SECONDS, + threadPrefix ); + } +} diff --git a/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/AsyncNotifyingListeningExecutorServiceTest.java b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/AsyncNotifyingListeningExecutorServiceTest.java new file mode 100644 index 0000000000..1a3bf0d4fd --- /dev/null +++ b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/AsyncNotifyingListeningExecutorServiceTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.yangtools.util.concurrent; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.After; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.Invoker; +import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_CALLABLE; +import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_RUNNABLE; +import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_RUNNABLE_WITH_RESULT; + +/** + * Unit tests for AsyncNotifyingListeningExecutorService. + * + * @author Thomas Pantelis + */ +public class AsyncNotifyingListeningExecutorServiceTest { + + private ExecutorService listenerExecutor; + private AsyncNotifyingListeningExecutorService testExecutor; + + @After + public void tearDown() { + if( listenerExecutor != null ) { + listenerExecutor.shutdownNow(); + } + + if( testExecutor != null ) { + testExecutor.shutdownNow(); + } + } + + @Test + public void testListenerCallbackWithExecutor() throws InterruptedException { + + String listenerThreadPrefix = "ListenerThread"; + listenerExecutor = Executors.newFixedThreadPool( 3, + new ThreadFactoryBuilder().setNameFormat( listenerThreadPrefix + "-%d" ).build() ); + + testExecutor = new AsyncNotifyingListeningExecutorService( + Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder().setNameFormat( "SingleThread" ).build() ), + listenerExecutor ); + + testListenerCallback( testExecutor, SUBMIT_CALLABLE, listenerThreadPrefix ); + testListenerCallback( testExecutor, SUBMIT_RUNNABLE, listenerThreadPrefix ); + testListenerCallback( testExecutor, SUBMIT_RUNNABLE_WITH_RESULT, listenerThreadPrefix ); + } + + @Test + public void testListenerCallbackWithNoExecutor() throws InterruptedException { + + String listenerThreadPrefix = "SingleThread"; + testExecutor = new AsyncNotifyingListeningExecutorService( + Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder().setNameFormat( listenerThreadPrefix ).build() ), + null ); + + testListenerCallback( testExecutor, SUBMIT_CALLABLE, listenerThreadPrefix ); + testListenerCallback( testExecutor, SUBMIT_RUNNABLE, listenerThreadPrefix ); + testListenerCallback( testExecutor, SUBMIT_RUNNABLE_WITH_RESULT, listenerThreadPrefix ); + } + + static void testListenerCallback( AsyncNotifyingListeningExecutorService executor, + Invoker invoker, final String expListenerThreadPrefix ) throws InterruptedException { + + AtomicReference assertError = new AtomicReference<>(); + CountDownLatch futureNotifiedLatch = new CountDownLatch( 1 ); + CountDownLatch blockTaskLatch = new CountDownLatch( 1 ); + + // The blockTaskLatch is used to block the task from completing until we've added + // our listener to the Future. Otherwise, if the task completes quickly and the Future is + // set to done before we've added our listener, the call to ListenableFuture#addListener + // will immediately notify synchronously on this thread as Futures#addCallback defaults to + // a same thread executor. This would erroneously fail the test. + + ListenableFuture future = invoker.invokeExecutor( executor, blockTaskLatch ); + addCallback( future, futureNotifiedLatch, expListenerThreadPrefix, assertError ); + + // Now that we've added our listener, signal the latch to let the task complete. + + blockTaskLatch.countDown(); + + assertTrue( "ListenableFuture callback was not notified of onSuccess", + futureNotifiedLatch.await( 5, TimeUnit.SECONDS ) ); + + if( assertError.get() != null ) { + throw assertError.get(); + } + + // Add another listener - since the Future is already complete, we expect the listener to be + // notified inline on this thread when it's added. + + futureNotifiedLatch = new CountDownLatch( 1 ); + addCallback( future, futureNotifiedLatch, Thread.currentThread().getName(), assertError ); + + assertTrue( "ListenableFuture callback was not notified of onSuccess", + futureNotifiedLatch.await( 5, TimeUnit.SECONDS ) ); + + if( assertError.get() != null ) { + throw assertError.get(); + } + } + + static void addCallback( ListenableFuture future, + final CountDownLatch futureNotifiedLatch, + final String expListenerThreadPrefix, + final AtomicReference assertError ) { + + Futures.addCallback( future, new FutureCallback() { + @Override + public void onSuccess( Object result ) { + + try { + String theadName = Thread.currentThread().getName(); + assertTrue( "ListenableFuture callback was not notified on the listener executor." + + " Expected thread name prefix \"" + expListenerThreadPrefix + + "\". Actual thread name \"" + theadName + "\"", + theadName.startsWith( expListenerThreadPrefix ) ); + } catch( AssertionError e ) { + assertError.set( e ); + } finally { + futureNotifiedLatch.countDown(); + } + } + + @Override + public void onFailure( Throwable t ) { + // Shouldn't happen + t.printStackTrace(); + } + } ); + } + + @Test + public void testDelegatedMethods() throws InterruptedException { + + Runnable task = new Runnable() { + @Override + public void run(){ + } + }; + + List taskList = Lists.newArrayList(); + + ExecutorService mockDelegate = mock( ExecutorService.class ); + doNothing().when( mockDelegate ).execute( task ); + doNothing().when( mockDelegate ).shutdown(); + doReturn( taskList ).when( mockDelegate ).shutdownNow(); + doReturn( true ).when( mockDelegate ).awaitTermination( 3, TimeUnit.SECONDS ); + doReturn( true ).when( mockDelegate ).isShutdown(); + doReturn( true ).when( mockDelegate ).isTerminated(); + + AsyncNotifyingListeningExecutorService executor = new AsyncNotifyingListeningExecutorService( + mockDelegate, null ); + + executor.execute( task ); + executor.shutdown(); + assertEquals( "awaitTermination", true, executor.awaitTermination( 3, TimeUnit.SECONDS ) ); + assertSame( "shutdownNow", taskList, executor.shutdownNow() ); + assertEquals( "isShutdown", true, executor.isShutdown() ); + assertEquals( "isTerminated", true, executor.isTerminated() ); + + verify( mockDelegate ).execute( task ); + verify( mockDelegate ).shutdown(); + verify( mockDelegate ).awaitTermination( 3, TimeUnit.SECONDS ); + verify( mockDelegate ).shutdownNow(); + verify( mockDelegate ).isShutdown(); + verify( mockDelegate ).isTerminated(); + } +} diff --git a/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/CommonTestUtils.java b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/CommonTestUtils.java new file mode 100644 index 0000000000..60c56a452c --- /dev/null +++ b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/CommonTestUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.yangtools.util.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.Uninterruptibles; + +/** + * Some common test utilities. + * + * @author Thomas Pantelis + */ +public class CommonTestUtils { + + public interface Invoker { + ListenableFuture invokeExecutor( ListeningExecutorService executor, + CountDownLatch blockingLatch ); + }; + + public static final Invoker SUBMIT_CALLABLE = new Invoker() { + @Override + public ListenableFuture invokeExecutor( ListeningExecutorService executor, + final CountDownLatch blockingLatch ) { + return executor.submit( new Callable() { + @Override + public Void call() throws Exception { + if( blockingLatch != null ) { + Uninterruptibles.awaitUninterruptibly( blockingLatch ); + } + return null; + } + } ); + } + }; + + public static final Invoker SUBMIT_RUNNABLE = new Invoker() { + @Override + public ListenableFuture invokeExecutor( ListeningExecutorService executor, + final CountDownLatch blockingLatch ) { + return executor.submit( new Runnable() { + @Override + public void run() { + if( blockingLatch != null ) { + Uninterruptibles.awaitUninterruptibly( blockingLatch ); + } + } + } ); + } + }; + + public static final Invoker SUBMIT_RUNNABLE_WITH_RESULT = new Invoker() { + @Override + public ListenableFuture invokeExecutor( ListeningExecutorService executor, + final CountDownLatch blockingLatch ) { + return executor.submit( new Runnable() { + @Override + public void run() { + if( blockingLatch != null ) { + Uninterruptibles.awaitUninterruptibly( blockingLatch ); + } + } + }, "foo" ); + } + }; +} diff --git a/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/DeadlockDetectingListeningExecutorServiceTest.java b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/DeadlockDetectingListeningExecutorServiceTest.java index b23750da98..6bba351dca 100644 --- a/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/DeadlockDetectingListeningExecutorServiceTest.java +++ b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/DeadlockDetectingListeningExecutorServiceTest.java @@ -13,10 +13,12 @@ import static org.junit.Assert.*; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -25,6 +27,13 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import static org.opendaylight.yangtools.util.concurrent.AsyncNotifyingListeningExecutorServiceTest.testListenerCallback; +import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.Invoker; +import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_CALLABLE; +import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_RUNNABLE; +import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_RUNNABLE_WITH_RESULT; /** * Unit tests for DeadlockDetectingListeningExecutorService. @@ -33,44 +42,6 @@ import com.google.common.util.concurrent.ListeningExecutorService; */ public class DeadlockDetectingListeningExecutorServiceTest { - interface Invoker { - ListenableFuture invokeExecutor( ListeningExecutorService executor ); - }; - - static final Invoker SUBMIT_CALLABLE = new Invoker() { - @Override - public ListenableFuture invokeExecutor( ListeningExecutorService executor ) { - return executor.submit( new Callable() { - @Override - public String call() throws Exception{ - return "foo"; - } - } ); - } - }; - - static final Invoker SUBMIT_RUNNABLE = new Invoker() { - @Override - public ListenableFuture invokeExecutor( ListeningExecutorService executor ) { - return executor.submit( new Runnable() { - @Override - public void run(){ - } - } ); - } - }; - - static final Invoker SUBMIT_RUNNABLE_WITH_RESULT = new Invoker() { - @Override - public ListenableFuture invokeExecutor( ListeningExecutorService executor ) { - return executor.submit( new Runnable() { - @Override - public void run(){ - } - }, "foo" ); - } - }; - interface InitialInvoker { void invokeExecutor( ListeningExecutorService executor, Runnable task ); }; @@ -104,13 +75,25 @@ public class DeadlockDetectingListeningExecutorServiceTest { @Before public void setup() { - executor = new DeadlockDetectingListeningExecutorService( Executors.newSingleThreadExecutor(), - DEADLOCK_EXECUTOR_FUNCTION ); + } + + @After + public void tearDown() { + if( executor != null ) { + executor.shutdownNow(); + } + } + + DeadlockDetectingListeningExecutorService newExecutor() { + return new DeadlockDetectingListeningExecutorService( Executors.newSingleThreadExecutor(), + DEADLOCK_EXECUTOR_FUNCTION ); } @Test public void testBlockingSubmitOffExecutor() throws Exception { + executor = newExecutor(); + // Test submit with Callable. ListenableFuture future = executor.submit( new Callable() { @@ -144,6 +127,8 @@ public class DeadlockDetectingListeningExecutorServiceTest { @Test public void testNonBlockingSubmitOnExecutorThread() throws Throwable { + executor = newExecutor(); + testNonBlockingSubmitOnExecutorThread( SUBMIT, SUBMIT_CALLABLE ); testNonBlockingSubmitOnExecutorThread( SUBMIT, SUBMIT_RUNNABLE ); testNonBlockingSubmitOnExecutorThread( SUBMIT, SUBMIT_RUNNABLE_WITH_RESULT ); @@ -162,7 +147,7 @@ public class DeadlockDetectingListeningExecutorServiceTest { @Override public void run() { - Futures.addCallback( invoker.invokeExecutor( executor ), new FutureCallback() { + Futures.addCallback( invoker.invokeExecutor( executor, null ), new FutureCallback() { @Override public void onSuccess( Object result ) { futureCompletedLatch.countDown(); @@ -191,6 +176,8 @@ public class DeadlockDetectingListeningExecutorServiceTest { @Test public void testBlockingSubmitOnExecutorThread() throws Exception { + executor = newExecutor(); + testBlockingSubmitOnExecutorThread( SUBMIT, SUBMIT_CALLABLE ); testBlockingSubmitOnExecutorThread( SUBMIT, SUBMIT_RUNNABLE ); testBlockingSubmitOnExecutorThread( SUBMIT, SUBMIT_RUNNABLE_WITH_RESULT ); @@ -209,7 +196,7 @@ public class DeadlockDetectingListeningExecutorServiceTest { public void run() { try { - invoker.invokeExecutor( executor ).get(); + invoker.invokeExecutor( executor, null ).get(); } catch( ExecutionException e ) { caughtEx.set( e.getCause() ); } catch( Throwable e ) { @@ -229,4 +216,25 @@ public class DeadlockDetectingListeningExecutorServiceTest { assertNotNull( "Expected exception thrown", caughtEx.get() ); assertEquals( "Caught exception type", TestDeadlockException.class, caughtEx.get().getClass() ); } + + @Test + public void testListenableFutureCallbackWithExecutor() throws InterruptedException { + + String listenerThreadPrefix = "ListenerThread"; + ExecutorService listenerExecutor = Executors.newFixedThreadPool( 1, + new ThreadFactoryBuilder().setNameFormat( listenerThreadPrefix + "-%d" ).build() ); + + executor = new DeadlockDetectingListeningExecutorService( + Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder().setNameFormat( "SingleThread" ).build() ), + DEADLOCK_EXECUTOR_FUNCTION, listenerExecutor ); + + try { + testListenerCallback( executor, SUBMIT_CALLABLE, listenerThreadPrefix ); + testListenerCallback( executor, SUBMIT_RUNNABLE, listenerThreadPrefix ); + testListenerCallback( executor, SUBMIT_RUNNABLE_WITH_RESULT, listenerThreadPrefix ); + } finally { + listenerExecutor.shutdownNow(); + } + } } diff --git a/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/QueuedNotificationManagerTest.java b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/QueuedNotificationManagerTest.java new file mode 100644 index 0000000000..d7e0e503fe --- /dev/null +++ b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/QueuedNotificationManagerTest.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.yangtools.util.concurrent; + +import static org.junit.Assert.fail; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Test; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Uninterruptibles; + +/** + * Unit tests for QueuedNotificationManager. + * + * @author Thomas Pantelis + */ +public class QueuedNotificationManagerTest { + + static class TestListener { + + private final List actual; + private volatile int expCount; + private volatile CountDownLatch latch; + volatile long sleepTime = 0; + volatile RuntimeException runtimeEx; + volatile Error jvmError; + boolean cacheNotifications = true; + String name; + + TestListener( int expCount, int id ) { + name = "TestListener " + id; + actual = Collections.synchronizedList( Lists.newArrayListWithCapacity( expCount ) ); + reset( expCount ); + } + + void reset( int expCount ) { + this.expCount = expCount; + latch = new CountDownLatch( expCount ); + actual.clear(); + } + + void onNotification( N data ) { + + try { + if( sleepTime > 0 ) { + Uninterruptibles.sleepUninterruptibly( sleepTime, TimeUnit.MILLISECONDS ); + } + + if( cacheNotifications ) { + actual.add( data ); + } + + RuntimeException localRuntimeEx = runtimeEx; + if( localRuntimeEx != null ) { + runtimeEx = null; + throw localRuntimeEx; + } + + Error localJvmError = jvmError; + if( localJvmError != null ) { + jvmError = null; + throw localJvmError; + } + + } finally { + latch.countDown(); + } + } + + void verifyNotifications() { + boolean done = Uninterruptibles.awaitUninterruptibly( latch, 10, TimeUnit.SECONDS ); + if( !done ) { + long actualCount = latch.getCount(); + fail( name + ": Received " + (expCount - actualCount) + + " notifications. Expected " + expCount ); + } + } + + void verifyNotifications( List expected ) { + verifyNotifications(); + assertEquals( name + ": Notifications", Lists.newArrayList( expected ), actual ); + } + + // Implement bad hashCode/equals methods to verify it doesn't screw up the + // QueuedNotificationManager as it should use reference identity. + @Override + public int hashCode(){ + return 1; + } + + @Override + public boolean equals( Object obj ){ + TestListener other = (TestListener) obj; + return other != null; + } + } + + static class TestListener2 extends TestListener { + TestListener2( int expCount, int id ) { + super(expCount, id); + } + } + + static class TestListener3 extends TestListener { + TestListener3( int expCount, int id ) { + super(expCount, id); + } + } + + static class TestNotifier implements QueuedNotificationManager.Invoker,N> { + + @Override + public void invokeListener( TestListener listener, N notification ) { + listener.onNotification( notification ); + } + } + + private ExecutorService queueExecutor; + + @After + public void tearDown() { + if( queueExecutor != null ) { + queueExecutor.shutdownNow(); + } + } + + @Test(timeout=10000) + public void testNotificationsWithSingleListener() { + + queueExecutor = Executors.newFixedThreadPool( 2 ); + NotificationManager, Integer> manager = + new QueuedNotificationManager<>( queueExecutor, new TestNotifier(), + 10, "TestMgr" ); + + int initialCount = 6; + int nNotifications = 100; + + TestListener listener = new TestListener<>( nNotifications, 1 ); + listener.sleepTime = 20; + + manager.submitNotifications( listener, Arrays.asList( 1, 2 ) ); + manager.submitNotification( listener, 3 ); + manager.submitNotifications( listener, Arrays.asList( 4, 5 ) ); + manager.submitNotification( listener, 6 ); + + manager.submitNotifications( null, Collections.emptyList() ); + manager.submitNotifications( listener, null ); + manager.submitNotification( listener, null ); + + Uninterruptibles.sleepUninterruptibly( 100, TimeUnit.MILLISECONDS ); + + listener.sleepTime = 0; + + List expNotifications = Lists.newArrayListWithCapacity( nNotifications ); + expNotifications.addAll( Arrays.asList( 1, 2, 3, 4, 5, 6 ) ); + for( int i = 1; i <= nNotifications - initialCount; i++ ) { + Integer v = Integer.valueOf( initialCount + i ); + expNotifications.add( v ); + manager.submitNotification( listener, v ); + } + + listener.verifyNotifications( expNotifications ); + } + + @Test + public void testNotificationsWithMultipleListeners() { + + int nListeners = 10; + queueExecutor = Executors.newFixedThreadPool( nListeners ); + final ExecutorService stagingExecutor = Executors.newFixedThreadPool( nListeners ); + final NotificationManager, Integer> manager = + new QueuedNotificationManager<>( queueExecutor, new TestNotifier(), + 5000, "TestMgr" ); + + final int nNotifications = 100000; + + System.out.println( "Testing " + nListeners + " listeners with " + nNotifications + + " notifications each..." ); + + final Integer[] notifications = new Integer[nNotifications]; + for( int i = 1; i <= nNotifications; i++ ) { + notifications[i-1] = Integer.valueOf( i ); + } + + Stopwatch stopWatch = new Stopwatch(); + stopWatch.start(); + + List> listeners = Lists.newArrayList(); + for( int i = 1; i <= nListeners; i++ ) { + final TestListener listener = + i == 2 ? new TestListener2( nNotifications, i ) : + i == 3 ? new TestListener3( nNotifications, i ) : + new TestListener( nNotifications, i ); + listeners.add( listener ); + + new Thread( new Runnable() { + @Override + public void run() { + for( int j = 1; j <= nNotifications; j++ ) { + final Integer n = notifications[j-1]; + stagingExecutor.execute( new Runnable() { + @Override + public void run() { + manager.submitNotification( listener, n ); + } + } ); + } + } + } ).start(); + } + + try { + for( TestListener listener: listeners ) { + listener.verifyNotifications(); + System.out.println( listener.name + " succeeded" ); + } + } finally { + stagingExecutor.shutdownNow(); + } + + stopWatch.stop(); + + System.out.println( "Elapsed time: " + stopWatch ); + System.out.println( queueExecutor ); + } + + @Test(timeout=10000) + public void testNotificationsWithListenerRuntimeEx() { + + queueExecutor = Executors.newFixedThreadPool( 1 ); + NotificationManager, Integer> manager = + new QueuedNotificationManager<>( queueExecutor, new TestNotifier(), + 10, "TestMgr" ); + + + TestListener listener = new TestListener<>( 2, 1 ); + listener.runtimeEx = new RuntimeException( "mock" ); + + manager.submitNotification( listener, 1 ); + manager.submitNotification( listener, 2 ); + + listener.verifyNotifications(); + } + + @Test(timeout=10000) + public void testNotificationsWithListenerJVMError() { + + final CountDownLatch errorCaughtLatch = new CountDownLatch( 1 ); + queueExecutor = new ThreadPoolExecutor( 1, 1, 0, TimeUnit.SECONDS, + new LinkedBlockingQueue() ) { + @Override + public void execute( final Runnable command ) { + super.execute( new Runnable() { + @Override + public void run() { + try { + command.run(); + } catch( Error e ) { + errorCaughtLatch.countDown(); + } + } + }); + } + }; + + NotificationManager, Integer> manager = + new QueuedNotificationManager<>( queueExecutor, new TestNotifier(), + 10, "TestMgr" ); + + TestListener listener = new TestListener<>( 2, 1 ); + listener.jvmError = new Error( "mock" ); + + manager.submitNotification( listener, 1 ); + + assertEquals( "JVM Error caught", true, Uninterruptibles.awaitUninterruptibly( + errorCaughtLatch, 5, TimeUnit.SECONDS ) ); + + manager.submitNotification( listener, 2 ); + + listener.verifyNotifications(); + } +} diff --git a/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/ThreadPoolExecutorTest.java b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/ThreadPoolExecutorTest.java new file mode 100644 index 0000000000..8270e45d35 --- /dev/null +++ b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/ThreadPoolExecutorTest.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.util.concurrent; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.After; +import org.junit.Test; + +import com.google.common.base.Stopwatch; + +/** + * Tests various ThreadPoolExecutor implementations. + * + * @author Thomas Pantelis + */ +public class ThreadPoolExecutorTest { + + private ExecutorService executor; + + @After + public void tearDown() { + if( executor != null ) { + executor.shutdownNow(); + } + } + + @Test + public void testFastThreadPoolExecution() throws Exception { + + testThreadPoolExecution( + SpecialExecutors.newBoundedFastThreadPool( 50, 100000, "TestPool" ), + 100000, "TestPool", 0 ); + } + + @Test(expected=RejectedExecutionException.class) + public void testFastThreadPoolRejectingTask() throws Exception { + + executor = SpecialExecutors.newBoundedFastThreadPool( 1, 1, "TestPool" ); + + for( int i = 0; i < 5; i++ ) { + executor.execute( new Task( null, null, null, null, + TimeUnit.MICROSECONDS.convert( 5, TimeUnit.SECONDS ) ) ); + } + } + + @Test + public void testBlockingFastThreadPoolExecution() throws Exception { + + // With a queue capacity of 1, it should block at some point. + testThreadPoolExecution( + SpecialExecutors.newBlockingBoundedFastThreadPool( 2, 1, "TestPool" ), + 1000, null, 10 ); + } + + @Test + public void testCachedThreadPoolExecution() throws Exception { + + testThreadPoolExecution( + SpecialExecutors.newBoundedCachedThreadPool( 10, 100000, "TestPool" ), + 100000, "TestPool", 0 ); + } + + @Test(expected=RejectedExecutionException.class) + public void testCachedThreadRejectingTask() throws Exception { + + ExecutorService executor = SpecialExecutors.newBoundedCachedThreadPool( 1, 1, "TestPool" ); + + for( int i = 0; i < 5; i++ ) { + executor.execute( new Task( null, null, null, null, + TimeUnit.MICROSECONDS.convert( 5, TimeUnit.SECONDS ) ) ); + } + } + + @Test + public void testBlockingCachedThreadPoolExecution() throws Exception { + + testThreadPoolExecution( + SpecialExecutors.newBlockingBoundedCachedThreadPool( 2, 1, "TestPool" ), + 1000, null, 10 ); + } + + void testThreadPoolExecution( final ExecutorService executor, + final int numTasksToRun, final String expThreadPrefix, final long taskDelay ) throws Exception { + + this.executor = executor; + + System.out.println( "\nTesting " + executor.getClass().getSimpleName() + " with " + + numTasksToRun + " tasks." ); + + final CountDownLatch tasksRunLatch = new CountDownLatch( numTasksToRun ); + final ConcurrentMap taskCountPerThread = new ConcurrentHashMap<>(); + final AtomicReference threadError = new AtomicReference<>(); + + Stopwatch stopWatch = new Stopwatch(); + stopWatch.start(); + + new Thread() { + @Override + public void run() { + for( int i = 0; i < numTasksToRun; i++ ) { +// if(i%100 == 0) { +// Uninterruptibles.sleepUninterruptibly( 20, TimeUnit.MICROSECONDS ); +// } + + executor.execute( new Task( tasksRunLatch, taskCountPerThread, + threadError, expThreadPrefix, taskDelay ) ); + } + } + }.start(); + + boolean done = tasksRunLatch.await( 15, TimeUnit.SECONDS ); + + stopWatch.stop(); + + if( !done ) { + fail( (numTasksToRun - tasksRunLatch.getCount()) + " tasks out of " + + numTasksToRun + " executed" ); + } + + if( threadError.get() != null ) { + throw threadError.get(); + } + + System.out.println( taskCountPerThread.size() + " threads used:" ); + for( Map.Entry e : taskCountPerThread.entrySet() ) { + System.out.println( " " + e.getKey().getName() + " - " + e.getValue() + " tasks" ); + } + + System.out.println( "\n" + executor ); + System.out.println( "\nElapsed time: " + stopWatch ); + System.out.println(); + } + + private static class Task implements Runnable { + final CountDownLatch tasksRunLatch; + final ConcurrentMap taskCountPerThread; + final AtomicReference threadError; + final String expThreadPrefix; + final long delay; + + Task( CountDownLatch tasksRunLatch, ConcurrentMap taskCountPerThread, + AtomicReference threadError, String expThreadPrefix, long delay ) { + this.tasksRunLatch = tasksRunLatch; + this.taskCountPerThread = taskCountPerThread; + this.threadError = threadError; + this.expThreadPrefix = expThreadPrefix; + this.delay = delay; + } + + @Override + public void run() { + try { + if( delay > 0 ) { + try { + TimeUnit.MICROSECONDS.sleep( delay ); + } catch( InterruptedException e ) {} + } + + if( expThreadPrefix != null ) { + assertEquals( "Thread name starts with " + expThreadPrefix, true, + Thread.currentThread().getName().startsWith( expThreadPrefix ) ); + } + + if( taskCountPerThread != null ) { + AtomicLong count = taskCountPerThread.get( Thread.currentThread() ); + if( count == null ) { + count = new AtomicLong( 0 ); + AtomicLong prev = taskCountPerThread.putIfAbsent( Thread.currentThread(), count ); + if( prev != null ) { + count = prev; + } + } + + count.incrementAndGet(); + } + + } catch( AssertionError e ) { + if( threadError != null ) { + threadError.set( e ); + } + } finally { + if( tasksRunLatch != null ) { + tasksRunLatch.countDown(); + } + } + } + } +} diff --git a/yang/pom.xml b/yang/pom.xml index a8eef46d22..a7e7cc77ad 100644 --- a/yang/pom.xml +++ b/yang/pom.xml @@ -29,7 +29,7 @@ yang-model-util yang-parser-api yang-parser-impl - yang-data-json + yang-data-composite-node diff --git a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/ClassLoaderUtils.java b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/ClassLoaderUtils.java index fab50614f2..e9ddbfb2f1 100644 --- a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/ClassLoaderUtils.java +++ b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/ClassLoaderUtils.java @@ -9,6 +9,10 @@ package org.opendaylight.yangtools.yang.binding.util; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; + import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; @@ -21,10 +25,10 @@ import java.util.concurrent.locks.Lock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Joiner; -import com.google.common.base.Preconditions; -import com.google.common.base.Supplier; - +/** + * @deprecated Use {@link org.opendaylight.yangtools.util.ClassLoaderUtils} instead. + */ +@Deprecated public final class ClassLoaderUtils { private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderUtils.class); @@ -58,17 +62,17 @@ public final class ClassLoaderUtils { } /** - * - * Runs {@link Callable} with provided {@link ClassLoader}. - * - * Invokes supplies function and makes sure that original {@link ClassLoader} - * is context {@link ClassLoader} after execution. - * - * @param cls {@link ClassLoader} to be used. - * @param function Function to be executed. - * @return Result of callable invocation. - * - */ + * + * Runs {@link Callable} with provided {@link ClassLoader}. + * + * Invokes supplies function and makes sure that original {@link ClassLoader} + * is context {@link ClassLoader} after execution. + * + * @param cls {@link ClassLoader} to be used. + * @param function Function to be executed. + * @return Result of callable invocation. + * + */ public static V withClassLoader(final ClassLoader cls, final Callable function) throws Exception { checkNotNull(cls, "Classloader should not be null"); checkNotNull(function, "Function should not be null"); @@ -82,20 +86,20 @@ public final class ClassLoaderUtils { } } - /** - * - * Runs {@link Callable} with provided {@link ClassLoader} and Lock. - * - * Invokes supplies function after acquiring lock - * and makes sure that original {@link ClassLoader} - * is context {@link ClassLoader} and lock is unlocked - * after execution. - * - * @param cls {@link ClassLoader} to be used. - * @param function Function to be executed. - * @return Result of Callable invocation. - * - */ + /** + * + * Runs {@link Callable} with provided {@link ClassLoader} and Lock. + * + * Invokes supplies function after acquiring lock + * and makes sure that original {@link ClassLoader} + * is context {@link ClassLoader} and lock is unlocked + * after execution. + * + * @param cls {@link ClassLoader} to be used. + * @param function Function to be executed. + * @return Result of Callable invocation. + * + */ public static V withClassLoaderAndLock(final ClassLoader cls, final Lock lock, final Supplier function) { checkNotNull(lock, "Lock should not be null"); @@ -138,9 +142,9 @@ public final class ClassLoaderUtils { String potentialOuter; int length = components.length; if (length > 2 && (potentialOuter = components[length - 2]) != null && Character.isUpperCase(potentialOuter.charAt(0))) { - String outerName = Joiner.on(".").join(Arrays.asList(components).subList(0, length - 1)); - String innerName = outerName + "$" + components[length-1]; - return cls.loadClass(innerName); + String outerName = Joiner.on(".").join(Arrays.asList(components).subList(0, length - 1)); + String innerName = outerName + "$" + components[length-1]; + return cls.loadClass(innerName); } else { throw e; } diff --git a/yang/yang-data-json/pom.xml b/yang/yang-data-composite-node/pom.xml similarity index 76% rename from yang/yang-data-json/pom.xml rename to yang/yang-data-composite-node/pom.xml index 228b9d82d2..d4b1533cd2 100644 --- a/yang/yang-data-json/pom.xml +++ b/yang/yang-data-composite-node/pom.xml @@ -15,9 +15,10 @@ 4.0.0 - yang-data-json + yang-data-composite-node ${project.artifactId} ${project.artifactId} + bundle @@ -44,4 +45,20 @@ + + + + org.apache.felix + maven-bundle-plugin + true + + + Composite node Normalized node transformator + * + + + + + + diff --git a/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/AnyXmlNodeCnSnParser.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/AnyXmlNodeCnSnParser.java new file mode 100644 index 0000000000..a13dd4bcf1 --- /dev/null +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/AnyXmlNodeCnSnParser.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser; + +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.AnyXmlNodeBaseParser; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; + +public class AnyXmlNodeCnSnParser extends AnyXmlNodeBaseParser> { + + public AnyXmlNodeCnSnParser() { + super(); + } + + @Override + protected Node parseAnyXml(Node element, AnyXmlSchemaNode schema) { + return element; + } +} diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/AugmentationNodeCnSnParser.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/AugmentationNodeCnSnParser.java similarity index 88% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/AugmentationNodeCnSnParser.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/AugmentationNodeCnSnParser.java index 60add9806e..452ec95ef1 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/AugmentationNodeCnSnParser.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/AugmentationNodeCnSnParser.java @@ -5,13 +5,14 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser; + +import org.opendaylight.yangtools.yang.data.composite.node.schema.json.CnSnToNormalizedNodesUtils; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.AugmentationNodeBaseParser; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.NodeParserDispatcher; -import org.opendaylight.yangtools.yang.data.json.schema.json.CnSnToNormalizedNodesUtils; import com.google.common.base.Preconditions; import com.google.common.collect.LinkedListMultimap; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/ChoiceNodeCnSnParser.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/ChoiceNodeCnSnParser.java similarity index 87% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/ChoiceNodeCnSnParser.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/ChoiceNodeCnSnParser.java index 6ab655992f..389ffcfea4 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/ChoiceNodeCnSnParser.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/ChoiceNodeCnSnParser.java @@ -5,13 +5,14 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser; + +import org.opendaylight.yangtools.yang.data.composite.node.schema.json.CnSnToNormalizedNodesUtils; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.ChoiceNodeBaseParser; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.NodeParserDispatcher; -import org.opendaylight.yangtools.yang.data.json.schema.json.CnSnToNormalizedNodesUtils; import com.google.common.base.Preconditions; import com.google.common.collect.LinkedListMultimap; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/CnSnToNormalizedNodeParserFactory.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/CnSnToNormalizedNodeParserFactory.java similarity index 90% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/CnSnToNormalizedNodeParserFactory.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/CnSnToNormalizedNodeParserFactory.java index 2cddf7d171..ce0e20ac58 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/CnSnToNormalizedNodeParserFactory.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/CnSnToNormalizedNodeParserFactory.java @@ -5,9 +5,10 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser; import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; @@ -18,6 +19,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.MapNode; import org.opendaylight.yangtools.yang.data.impl.schema.transform.ToNormalizedNodeParser; import org.opendaylight.yangtools.yang.data.impl.schema.transform.ToNormalizedNodeParserFactory; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.NodeParserDispatcher; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.ChoiceNode; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; @@ -35,11 +37,13 @@ public class CnSnToNormalizedNodeParserFactory implements ToNormalizedNodeParser private final MapEntryNodeCnSnParser mapEntryNodeCnSnParser; private final ChoiceNodeCnSnParser choiceNodeCnSnParser; private final AugmentationNodeCnSnParser augmentationNodeCnSnParser; + private final AnyXmlNodeCnSnParser anyXmlNodeCnSnParser; private CnSnToNormalizedNodeParserFactory() { leafNodeCnSnParser = new LeafNodeCnSnParser(); leafSetEntryNodeCnSnParser = new LeafSetEntryNodeCnSnParser(); leafSetNodeCnSnParser = new LeafSetNodeCnSnParser(leafSetEntryNodeCnSnParser); + anyXmlNodeCnSnParser = new AnyXmlNodeCnSnParser(); final NodeParserDispatcher> dispatcher = new NodeParserDispatcher.BaseNodeParserDispatcher>( this) { @@ -51,6 +55,7 @@ public class CnSnToNormalizedNodeParserFactory implements ToNormalizedNodeParser mapNodeCnSnParser = new MapNodeCnSnParser(mapEntryNodeCnSnParser); choiceNodeCnSnParser = new ChoiceNodeCnSnParser(dispatcher); augmentationNodeCnSnParser = new AugmentationNodeCnSnParser(dispatcher); + } public static CnSnToNormalizedNodeParserFactory getInstance() { @@ -96,4 +101,9 @@ public class CnSnToNormalizedNodeParserFactory implements ToNormalizedNodeParser public ToNormalizedNodeParser, MapEntryNode, ListSchemaNode> getMapEntryNodeParser() { return mapEntryNodeCnSnParser; } + + @Override + public ToNormalizedNodeParser, AnyXmlNode, AnyXmlSchemaNode> getAnyXmlNodeParser() { + return anyXmlNodeCnSnParser; + } } diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/ContainerNodeCnSnParser.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/ContainerNodeCnSnParser.java similarity index 89% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/ContainerNodeCnSnParser.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/ContainerNodeCnSnParser.java index afd3a1333a..623d81d058 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/ContainerNodeCnSnParser.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/ContainerNodeCnSnParser.java @@ -5,7 +5,9 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser; + +import org.opendaylight.yangtools.yang.data.composite.node.schema.json.CnSnToNormalizedNodesUtils; import java.util.Collections; import java.util.Map; @@ -14,7 +16,6 @@ import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.ContainerNodeBaseParser; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.NodeParserDispatcher; -import org.opendaylight.yangtools.yang.data.json.schema.json.CnSnToNormalizedNodesUtils; import com.google.common.base.Preconditions; import com.google.common.collect.LinkedListMultimap; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/LeafNodeCnSnParser.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/LeafNodeCnSnParser.java similarity index 92% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/LeafNodeCnSnParser.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/LeafNodeCnSnParser.java index 7a601eeb9e..cede56c0ac 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/LeafNodeCnSnParser.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/LeafNodeCnSnParser.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser; import java.util.Collections; import java.util.Map; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/LeafSetEntryNodeCnSnParser.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/LeafSetEntryNodeCnSnParser.java similarity index 93% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/LeafSetEntryNodeCnSnParser.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/LeafSetEntryNodeCnSnParser.java index bd3a3dff99..41a5a9e79e 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/LeafSetEntryNodeCnSnParser.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/LeafSetEntryNodeCnSnParser.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser; import java.util.Collections; import java.util.Map; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/LeafSetNodeCnSnParser.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/LeafSetNodeCnSnParser.java similarity index 93% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/LeafSetNodeCnSnParser.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/LeafSetNodeCnSnParser.java index 1b8f96ca7a..b0ffefacd0 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/LeafSetNodeCnSnParser.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/LeafSetNodeCnSnParser.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/MapEntryNodeCnSnParser.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/MapEntryNodeCnSnParser.java similarity index 89% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/MapEntryNodeCnSnParser.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/MapEntryNodeCnSnParser.java index ec3736ff3e..ad0b846d64 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/MapEntryNodeCnSnParser.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/MapEntryNodeCnSnParser.java @@ -5,7 +5,9 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser; + +import org.opendaylight.yangtools.yang.data.composite.node.schema.json.CnSnToNormalizedNodesUtils; import java.util.Collections; import java.util.Map; @@ -14,7 +16,6 @@ import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.MapEntryNodeBaseParser; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.NodeParserDispatcher; -import org.opendaylight.yangtools.yang.data.json.schema.json.CnSnToNormalizedNodesUtils; import com.google.common.base.Preconditions; import com.google.common.collect.LinkedListMultimap; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/MapNodeCnSnParser.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/MapNodeCnSnParser.java similarity index 93% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/MapNodeCnSnParser.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/MapNodeCnSnParser.java index 92fb1f3387..5fae7715b3 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/MapNodeCnSnParser.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/parser/MapNodeCnSnParser.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; diff --git a/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/AnyXmlNodeCnSnSerializer.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/AnyXmlNodeCnSnSerializer.java new file mode 100644 index 0000000000..db941ed556 --- /dev/null +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/AnyXmlNodeCnSnSerializer.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer; + +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.AnyXmlNodeBaseSerializer; + +public class AnyXmlNodeCnSnSerializer extends AnyXmlNodeBaseSerializer> { + + @Override + protected Node serializeAnyXml(AnyXmlNode node) { + return node.getValue(); + } +} diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/AugmentationNodeCnSnSerializer.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/AugmentationNodeCnSnSerializer.java similarity index 92% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/AugmentationNodeCnSnSerializer.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/AugmentationNodeCnSnSerializer.java index c71672b5cd..6aa294e405 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/AugmentationNodeCnSnSerializer.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/AugmentationNodeCnSnSerializer.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.serializer; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.AugmentationNodeBaseSerializer; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/ChoiceNodeCnSnSerializer.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/ChoiceNodeCnSnSerializer.java similarity index 92% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/ChoiceNodeCnSnSerializer.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/ChoiceNodeCnSnSerializer.java index 1542da779b..2a79170a5b 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/ChoiceNodeCnSnSerializer.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/ChoiceNodeCnSnSerializer.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.serializer; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.ChoiceNodeBaseSerializer; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/CnSnFromNormalizedNodeSerializerFactory.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/CnSnFromNormalizedNodeSerializerFactory.java similarity index 89% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/CnSnFromNormalizedNodeSerializerFactory.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/CnSnFromNormalizedNodeSerializerFactory.java index 2f771e629f..681c624903 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/CnSnFromNormalizedNodeSerializerFactory.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/CnSnFromNormalizedNodeSerializerFactory.java @@ -5,9 +5,10 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.serializer; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer; import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; @@ -19,6 +20,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.MapNode; import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializer; import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializerFactory; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; @@ -34,6 +36,7 @@ public final class CnSnFromNormalizedNodeSerializerFactory implements FromNormal private final MapNodeCnSnSerializer mapNodeSerializer; private final LeafSetEntryNodeCnSnSerializer leafSetEntryNodeSerializer; private final MapEntryNodeCnSnSerializer mapEntryNodeSerializer; + private final AnyXmlNodeCnSnSerializer anyXmlNodeSerializer; private CnSnFromNormalizedNodeSerializerFactory() { final NodeSerializerDispatcher.BaseNodeSerializerDispatcher> dispatcher = new NodeSerializerDispatcher.BaseNodeSerializerDispatcher>( @@ -45,6 +48,7 @@ public final class CnSnFromNormalizedNodeSerializerFactory implements FromNormal choiceSerializer = new ChoiceNodeCnSnSerializer(dispatcher); augmentSerializer = new AugmentationNodeCnSnSerializer(dispatcher); leafNodeSerializer = new LeafNodeCnSnSerializer(); + anyXmlNodeSerializer = new AnyXmlNodeCnSnSerializer(); leafSetEntryNodeSerializer = new LeafSetEntryNodeCnSnSerializer(); leafSetSerializer = new LeafSetNodeCnSnSerializer(leafSetEntryNodeSerializer); @@ -99,4 +103,9 @@ public final class CnSnFromNormalizedNodeSerializerFactory implements FromNormal public FromNormalizedNodeSerializer, MapEntryNode, ListSchemaNode> getMapEntryNodeSerializer() { return mapEntryNodeSerializer; } + + @Override + public FromNormalizedNodeSerializer, AnyXmlNode, AnyXmlSchemaNode> getAnyXmlNodeSerializer() { + return anyXmlNodeSerializer; + } } diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/ContainerNodeCnSnSerializer.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/ContainerNodeCnSnSerializer.java similarity index 62% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/ContainerNodeCnSnSerializer.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/ContainerNodeCnSnSerializer.java index 5c143e0f5e..7c2386b398 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/ContainerNodeCnSnSerializer.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/ContainerNodeCnSnSerializer.java @@ -5,20 +5,17 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.serializer; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer; import com.google.common.base.Preconditions; - import java.util.Collections; import java.util.List; - -import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode; -import org.opendaylight.yangtools.yang.data.api.MutableNode; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.impl.NodeFactory; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.ContainerNodeBaseSerializer; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; public class ContainerNodeCnSnSerializer extends ContainerNodeBaseSerializer> { @@ -30,19 +27,11 @@ public class ContainerNodeCnSnSerializer extends ContainerNodeBaseSerializer> serialize(final ContainerSchemaNode schema, final ContainerNode containerNode) { - - MutableCompositeNode mutCompNode = NodeFactory.createMutableCompositeNode(containerNode.getNodeType(), null, - null, null, null); - - for (Node element : super.serialize(schema, containerNode)) { - if (element instanceof MutableNode) { - ((MutableNode) element).setParent(mutCompNode); - } - mutCompNode.getValue().add(element); - } - - return Collections.>singletonList(mutCompNode); + public List> serialize(final ContainerSchemaNode schema, final ContainerNode node) { + CompositeNodeBuilder compNodeBuilder = ImmutableCompositeNode.builder(); + compNodeBuilder.setQName(node.getNodeType()); + compNodeBuilder.addAll(super.serialize(schema, node)); + return Collections.> singletonList(compNodeBuilder.toInstance()); } @Override diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/LeafNodeCnSnSerializer.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/LeafNodeCnSnSerializer.java similarity index 91% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/LeafNodeCnSnSerializer.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/LeafNodeCnSnSerializer.java index d17ac2e772..7a627b7b5f 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/LeafNodeCnSnSerializer.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/LeafNodeCnSnSerializer.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.serializer; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/LeafSetEntryNodeCnSnSerializer.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/LeafSetEntryNodeCnSnSerializer.java similarity index 92% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/LeafSetEntryNodeCnSnSerializer.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/LeafSetEntryNodeCnSnSerializer.java index 9f628e5616..cba8e48348 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/LeafSetEntryNodeCnSnSerializer.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/LeafSetEntryNodeCnSnSerializer.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.serializer; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/LeafSetNodeCnSnSerializer.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/LeafSetNodeCnSnSerializer.java similarity index 93% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/LeafSetNodeCnSnSerializer.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/LeafSetNodeCnSnSerializer.java index f784a53de6..6d3b22b424 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/LeafSetNodeCnSnSerializer.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/LeafSetNodeCnSnSerializer.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.serializer; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/MapEntryNodeCnSnSerializer.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/MapEntryNodeCnSnSerializer.java similarity index 60% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/MapEntryNodeCnSnSerializer.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/MapEntryNodeCnSnSerializer.java index 3d0fb2b57e..5adbd11e9e 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/MapEntryNodeCnSnSerializer.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/MapEntryNodeCnSnSerializer.java @@ -5,20 +5,17 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.serializer; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer; import com.google.common.base.Preconditions; - import java.util.Collections; import java.util.List; - -import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode; -import org.opendaylight.yangtools.yang.data.api.MutableNode; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; -import org.opendaylight.yangtools.yang.data.impl.NodeFactory; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.MapEntryNodeBaseSerializer; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; public class MapEntryNodeCnSnSerializer extends MapEntryNodeBaseSerializer> { @@ -30,19 +27,11 @@ public class MapEntryNodeCnSnSerializer extends MapEntryNodeBaseSerializer> serialize(final ListSchemaNode schema, final MapEntryNode node) { - - MutableCompositeNode mutCompNode = NodeFactory.createMutableCompositeNode(node.getNodeType(), null, null, null, - null); - - for (Node element : super.serialize(schema, node)) { - if (element instanceof MutableNode) { - ((MutableNode) element).setParent(mutCompNode); - } - mutCompNode.getValue().add(element); - } - - return Collections.>singletonList(mutCompNode); + public List> serialize(ListSchemaNode schema, MapEntryNode node) { + CompositeNodeBuilder compNodeBuilder = ImmutableCompositeNode.builder(); + compNodeBuilder.setQName(node.getNodeType()); + compNodeBuilder.addAll(super.serialize(schema, node)); + return Collections.> singletonList(compNodeBuilder.toInstance()); } @Override diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/MapNodeCnSnSerializer.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/MapNodeCnSnSerializer.java similarity index 93% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/MapNodeCnSnSerializer.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/MapNodeCnSnSerializer.java index ea4ed18406..f6107217c1 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/MapNodeCnSnSerializer.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/cnsn/serializer/MapNodeCnSnSerializer.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.serializer; +package org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; diff --git a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/json/CnSnToNormalizedNodesUtils.java b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/json/CnSnToNormalizedNodesUtils.java similarity index 93% rename from yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/json/CnSnToNormalizedNodesUtils.java rename to yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/json/CnSnToNormalizedNodesUtils.java index 7478aa5a24..6ab21040c6 100644 --- a/yang/yang-data-json/src/main/java/org/opendaylight/yangtools/yang/data/json/schema/json/CnSnToNormalizedNodesUtils.java +++ b/yang/yang-data-composite-node/src/main/java/org/opendaylight/yangtools/yang/data/composite/node/schema/json/CnSnToNormalizedNodesUtils.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.json; +package org.opendaylight.yangtools.yang.data.composite.node.schema.json; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.CompositeNode; diff --git a/yang/yang-data-json/src/test/java/org/opendaylight/yangtools/yang/data/json/schema/TestUtils.java b/yang/yang-data-composite-node/src/test/java/org/opendaylight/yangtools/yang/data/composite/node/schema/TestUtils.java similarity index 69% rename from yang/yang-data-json/src/test/java/org/opendaylight/yangtools/yang/data/json/schema/TestUtils.java rename to yang/yang-data-composite-node/src/test/java/org/opendaylight/yangtools/yang/data/composite/node/schema/TestUtils.java index f6eb9a3cc5..d2d2a3de3a 100644 --- a/yang/yang-data-json/src/test/java/org/opendaylight/yangtools/yang/data/json/schema/TestUtils.java +++ b/yang/yang-data-composite-node/src/test/java/org/opendaylight/yangtools/yang/data/composite/node/schema/TestUtils.java @@ -5,10 +5,9 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema; +package org.opendaylight.yangtools.yang.data.composite.node.schema; import static org.junit.Assert.assertNotNull; -import static org.opendaylight.yangtools.yang.data.impl.NodeFactory.createMutableCompositeNode; import static org.opendaylight.yangtools.yang.data.impl.NodeFactory.createMutableSimpleNode; import java.io.File; @@ -25,24 +24,24 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; - import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; -import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode; -import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.data.impl.NodeFactory; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.parser.api.YangModelParser; @@ -125,69 +124,61 @@ public class TestUtils { * /cnsn-to-normalized-node/simple-conainer.json */ public static CompositeNode prepareCompositeNodeStruct() { - MutableCompositeNode cont = createMutableCompositeNode(QName.create(MODULE_BASE, "cont"), null, null, null, - null); + CompositeNodeBuilder contBuilder = ImmutableCompositeNode.builder(); + contBuilder.setQName(QName.create(MODULE_BASE, "cont")); // cont1 - List> contChilds = new ArrayList<>(); - contChilds.add(createMutableCompositeNode(QName.create(MODULE_BASE, "cont1"), - cont, - Collections.> emptyList(), null, null)); + contBuilder.add(ImmutableCompositeNode.builder().setQName(QName.create(MODULE_BASE, "cont1")).toInstance()); // cont2 - MutableCompositeNode cont2 = createMutableCompositeNode(QName.create(MODULE_BASE, "cont2"), cont, null, null, - null); - List> cont2Childs = new ArrayList<>(); - cont2Childs.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lf21"), cont2, - "value in cont2/lf21", null, null)); - cont2.setValue(cont2Childs); - contChilds.add(cont2); + CompositeNodeBuilder cont2 = ImmutableCompositeNode.builder().setQName( + QName.create(MODULE_BASE, "cont2")); + cont2.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lf21"), null, "value in cont2/lf21", null, null)); + contBuilder.add(cont2.toInstance()); // lst1 - contChilds.add(createMutableCompositeNode(QName.create(MODULE_BASE, "lst1"), cont, - Collections.> emptyList(), null, null)); + contBuilder.add(ImmutableCompositeNode.builder().setQName(QName.create(MODULE_BASE, "lst1")).toInstance()); // lst2 - MutableCompositeNode lst2_1 = createMutableCompositeNode(QName.create(MODULE_BASE, "lst2"), cont, null, null, - null); - List> lst2_1Childs = new ArrayList<>(); - lst2_1Childs - .add(createMutableSimpleNode(QName.create(MODULE_BASE, "lf21"), - lst2_1, - "some value21", null, null)); - lst2_1.setValue(lst2_1Childs); - contChilds.add(lst2_1); - - MutableCompositeNode lst2_2 = createMutableCompositeNode(QName.create(MODULE_BASE, "lst2"), cont, null, null, - null); - List> lst2_2Childs = new ArrayList<>(); - lst2_2Childs - .add(createMutableSimpleNode(QName.create(MODULE_BASE, "lf22"), lst2_2, "some value22", null, null)); - lst2_2.setValue(lst2_2Childs); - contChilds.add(lst2_2); + CompositeNodeBuilder lst2_1Builder = ImmutableCompositeNode.builder().setQName( + QName.create(MODULE_BASE, "lst2")); + lst2_1Builder.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lf21"), null, "some value21", null, null)); + contBuilder.add(lst2_1Builder.toInstance()); + + CompositeNodeBuilder lst2_2Builder = ImmutableCompositeNode.builder().setQName( + QName.create(MODULE_BASE, "lst2")); + lst2_2Builder.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lf22"), null, "some value22", null, null)); + contBuilder.add(lst2_2Builder.toInstance()); // lflst1 - contChilds.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lflst1"), cont, "lflst1_1", null, null)); - contChilds.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lflst1"), cont, "lflst1_2", null, null)); + contBuilder.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lflst1"), null, "lflst1_1", null, null)); + contBuilder.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lflst1"), null, "lflst1_2", null, null)); // lf1 - contChilds.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lf1"), cont, "lf1", null, null)); + contBuilder.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lf1"), null, "lf1", null, null)); // lf11 - contChilds.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lf11"), cont, "value from case (cs1)", null, + contBuilder.add(createMutableSimpleNode(QName.create(MODULE_BASE, "lf11"), null, "value from case (cs1)", null, null)); // cont3 - MutableCompositeNode cont3 = createMutableCompositeNode(QName.create(MODULE_AUGMENT, "cont3"), cont, null, - null, null); - List> cont3Childs = new ArrayList<>(); - cont3Childs.add(createMutableSimpleNode(QName.create(MODULE_AUGMENT, "lf31"), cont3, + CompositeNodeBuilder cont3Builder = ImmutableCompositeNode.builder().setQName( + QName.create(MODULE_AUGMENT, "cont3")); + cont3Builder.add(createMutableSimpleNode(QName.create(MODULE_AUGMENT, "lf31"), null, "value in leaf in augment", null, null)); - cont3.setValue(cont3Childs); - contChilds.add(cont3); + contBuilder.add(cont3Builder.toInstance()); + + // anxml-composite + CompositeNodeBuilder anxmlCompositeBuilder = ImmutableCompositeNode.builder().setQName( + QName.create(MODULE_BASE, "anxml-composite")); + anxmlCompositeBuilder.add(NodeFactory.createImmutableSimpleNode(QName.create(MODULE_BASE, "anxml-cont"), null, + null)); + contBuilder.add(anxmlCompositeBuilder.toInstance()); - cont.setValue(contChilds); - return cont; + // anxml-simple + contBuilder.add(NodeFactory.createImmutableSimpleNode(QName.create(MODULE_BASE, "anxml-simple"), null,43)); + + return contBuilder.toInstance(); } /** @@ -203,7 +194,7 @@ public class TestUtils { .withNodeIdentifier(getNodeIdentifier("cont2")) .withChild( Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("lf21")) - .withValue("value in cont2/lf21").build()).build()); + .withValue("value in cont2/lf21").build()).build()); CollectionNodeBuilder lst1 = Builders.mapBuilder().withNodeIdentifier( getNodeIdentifier("lst1")); @@ -249,7 +240,7 @@ public class TestUtils { .withNodeIdentifier(getNodeIdentifier("chc")) .withChild( Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("lf11")) - .withValue("value from case (cs1)").build()).build()); + .withValue("value from case (cs1)").build()).build()); Set children = new HashSet<>(); children.add(QName.create(MODULE_AUGMENT, "cont3")); @@ -259,11 +250,31 @@ public class TestUtils { .withNodeIdentifier(getAugmentationIdentifier(null, null, null, children)) .withChild( Builders.containerBuilder() - .withNodeIdentifier(getNodeIdentifier(MODULE_AUGMENT, "cont3")) - .withChild( - Builders.leafBuilder() - .withNodeIdentifier(getNodeIdentifier(MODULE_AUGMENT, "lf31")) - .withValue("value in leaf in augment").build()).build()).build()); + .withNodeIdentifier(getNodeIdentifier(MODULE_AUGMENT, "cont3")) + .withChild( + Builders.leafBuilder() + .withNodeIdentifier(getNodeIdentifier(MODULE_AUGMENT, "lf31")) + .withValue("value in leaf in augment").build()).build()).build()); + + containerBuilder.withChild(Builders + .anyXmlBuilder() + .withNodeIdentifier(getNodeIdentifier("anxml-composite")) + .withValue( + ImmutableCompositeNode + .builder() + .setQName(QName.create("simple:container:yang", "2013-11-12", "anxml-composite")) + .add(NodeFactory.createImmutableSimpleNode( + QName.create("simple:container:yang", "2013-11-12", "anxml-cont"), null, null)) + .toInstance()).build()); + + containerBuilder + .withChild(Builders + .anyXmlBuilder() + .withNodeIdentifier(getNodeIdentifier("anxml-simple")) + .withValue( + NodeFactory.createImmutableSimpleNode( + QName.create("simple:container:yang", "2013-11-12", "anxml-simple"), null, 43)) + .build()); ContainerNode build = containerBuilder.build(); return build; diff --git a/yang/yang-data-json/src/test/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/ParseCnSnStructToNormalizedStructTest.java b/yang/yang-data-composite-node/src/test/java/org/opendaylight/yangtools/yang/data/composite/node/schema/parser/ParseCnSnStructToNormalizedStructTest.java similarity index 87% rename from yang/yang-data-json/src/test/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/ParseCnSnStructToNormalizedStructTest.java rename to yang/yang-data-composite-node/src/test/java/org/opendaylight/yangtools/yang/data/composite/node/schema/parser/ParseCnSnStructToNormalizedStructTest.java index 223278c78c..56fd81274e 100644 --- a/yang/yang-data-json/src/test/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/parser/ParseCnSnStructToNormalizedStructTest.java +++ b/yang/yang-data-composite-node/src/test/java/org/opendaylight/yangtools/yang/data/composite/node/schema/parser/ParseCnSnStructToNormalizedStructTest.java @@ -5,10 +5,15 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser; +package org.opendaylight.yangtools.yang.data.composite.node.schema.parser; import static org.junit.Assert.assertEquals; +import org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser.CnSnToNormalizedNodeParserFactory; + + +import org.opendaylight.yangtools.yang.data.composite.node.schema.TestUtils; + import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @@ -19,7 +24,6 @@ import org.junit.Test; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.json.schema.TestUtils; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; diff --git a/yang/yang-data-json/src/test/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/SerializeNormalizedStructToCnSnStructTest.java b/yang/yang-data-composite-node/src/test/java/org/opendaylight/yangtools/yang/data/composite/node/schema/serializer/SerializeNormalizedStructToCnSnStructTest.java similarity index 88% rename from yang/yang-data-json/src/test/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/SerializeNormalizedStructToCnSnStructTest.java rename to yang/yang-data-composite-node/src/test/java/org/opendaylight/yangtools/yang/data/composite/node/schema/serializer/SerializeNormalizedStructToCnSnStructTest.java index 5e1283fbaf..95b0ec81d2 100644 --- a/yang/yang-data-json/src/test/java/org/opendaylight/yangtools/yang/data/json/schema/cnsn/serializer/SerializeNormalizedStructToCnSnStructTest.java +++ b/yang/yang-data-composite-node/src/test/java/org/opendaylight/yangtools/yang/data/composite/node/schema/serializer/SerializeNormalizedStructToCnSnStructTest.java @@ -5,11 +5,16 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.yangtools.yang.data.json.schema.cnsn.serializer; +package org.opendaylight.yangtools.yang.data.composite.node.schema.serializer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer.CnSnFromNormalizedNodeSerializerFactory; + + +import org.opendaylight.yangtools.yang.data.composite.node.schema.TestUtils; + import java.net.URISyntaxException; import java.util.Set; @@ -19,7 +24,6 @@ import org.junit.Test; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.json.schema.TestUtils; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; diff --git a/yang/yang-data-json/src/test/resources/cnsn-to-normalized-node/json/simple-container.json b/yang/yang-data-composite-node/src/test/resources/cnsn-to-normalized-node/json/simple-container.json similarity index 82% rename from yang/yang-data-json/src/test/resources/cnsn-to-normalized-node/json/simple-container.json rename to yang/yang-data-composite-node/src/test/resources/cnsn-to-normalized-node/json/simple-container.json index b840efbd8f..37dd7c0732 100644 --- a/yang/yang-data-json/src/test/resources/cnsn-to-normalized-node/json/simple-container.json +++ b/yang/yang-data-composite-node/src/test/resources/cnsn-to-normalized-node/json/simple-container.json @@ -27,5 +27,11 @@ "lf1":"lf1", "lf11":"value from case (cs1)" + "anxml-composite": { + "anxml-cont" { + } + } + + "anxml-simple":43; } } \ No newline at end of file diff --git a/yang/yang-data-json/src/test/resources/cnsn-to-normalized-node/yang/augment-simple-container.yang b/yang/yang-data-composite-node/src/test/resources/cnsn-to-normalized-node/yang/augment-simple-container.yang similarity index 100% rename from yang/yang-data-json/src/test/resources/cnsn-to-normalized-node/yang/augment-simple-container.yang rename to yang/yang-data-composite-node/src/test/resources/cnsn-to-normalized-node/yang/augment-simple-container.yang diff --git a/yang/yang-data-json/src/test/resources/cnsn-to-normalized-node/yang/simple-container.yang b/yang/yang-data-composite-node/src/test/resources/cnsn-to-normalized-node/yang/simple-container.yang similarity index 92% rename from yang/yang-data-json/src/test/resources/cnsn-to-normalized-node/yang/simple-container.yang rename to yang/yang-data-composite-node/src/test/resources/cnsn-to-normalized-node/yang/simple-container.yang index 127ba8f60c..b96b5488c7 100644 --- a/yang/yang-data-json/src/test/resources/cnsn-to-normalized-node/yang/simple-container.yang +++ b/yang/yang-data-composite-node/src/test/resources/cnsn-to-normalized-node/yang/simple-container.yang @@ -43,5 +43,7 @@ module simple-container-yang { } } + anyxml anxml-composite; + anyxml anxml-simple; } } diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/FromNormalizedNodeSerializerFactory.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/FromNormalizedNodeSerializerFactory.java index 48fedda860..319752d282 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/FromNormalizedNodeSerializerFactory.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/FromNormalizedNodeSerializerFactory.java @@ -7,6 +7,7 @@ */ package org.opendaylight.yangtools.yang.data.impl.schema.transform; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; @@ -15,6 +16,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; @@ -36,4 +38,5 @@ public interface FromNormalizedNodeSerializerFactory { FromNormalizedNodeSerializer, LeafListSchemaNode> getLeafSetNodeSerializer(); FromNormalizedNodeSerializer getMapEntryNodeSerializer(); FromNormalizedNodeSerializer getMapNodeSerializer(); + FromNormalizedNodeSerializer getAnyXmlNodeSerializer(); } diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/ToNormalizedNodeParserFactory.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/ToNormalizedNodeParserFactory.java index f99cbcff28..7c2700f7c4 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/ToNormalizedNodeParserFactory.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/ToNormalizedNodeParserFactory.java @@ -7,6 +7,7 @@ */ package org.opendaylight.yangtools.yang.data.impl.schema.transform; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; @@ -15,6 +16,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; @@ -36,4 +38,5 @@ public interface ToNormalizedNodeParserFactory { ToNormalizedNodeParser, LeafListSchemaNode> getLeafSetNodeParser(); ToNormalizedNodeParser getMapEntryNodeParser(); ToNormalizedNodeParser getMapNodeParser(); + ToNormalizedNodeParser getAnyXmlNodeParser(); } diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/parser/AnyXmlNodeBaseParser.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/parser/AnyXmlNodeBaseParser.java new file mode 100644 index 0000000000..27303670b3 --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/parser/AnyXmlNodeBaseParser.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeAttrBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.ToNormalizedNodeParser; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; + +/** + * Abstract(base) parser for LeafNodes, parses elements of type E. + * + * @param type of elements to be parsed + */ +public abstract class AnyXmlNodeBaseParser implements + ToNormalizedNodeParser { + + @Override + public final AnyXmlNode parse(Iterable elements, AnyXmlSchemaNode schema) { + final int size = Iterables.size(elements); + Preconditions.checkArgument(size == 1, "Elements mapped to any-xml node illegal count: %s", size); + + final E e = elements.iterator().next(); + Node value = parseAnyXml(e, schema); + + NormalizedNodeAttrBuilder, AnyXmlNode> anyXmlBuilder = Builders.anyXmlBuilder(schema); + + return anyXmlBuilder.withValue(value).build(); + } + + /** + * + * Parse the inner value of a AnyXmlNode from element of type E. + * + * @param element to be parsed + * @param schema schema for leaf + * @return parsed element as an Object + */ + protected abstract Node parseAnyXml(E element, AnyXmlSchemaNode schema); + +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/parser/NodeParserDispatcher.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/parser/NodeParserDispatcher.java index 40722b3aee..c316e72b02 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/parser/NodeParserDispatcher.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/parser/NodeParserDispatcher.java @@ -7,10 +7,11 @@ */ package org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser; +import com.google.common.base.Preconditions; import java.util.List; - import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.impl.schema.transform.ToNormalizedNodeParserFactory; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.ChoiceNode; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; @@ -18,8 +19,6 @@ import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; -import com.google.common.base.Preconditions; - /** * * Dispatches the parsing process of elements according to schema and returns the parsed Node. @@ -57,6 +56,8 @@ public interface NodeParserDispatcher { return factory.getChoiceNodeParser().parse(childNodes, (ChoiceNode) schema); } else if (schema instanceof AugmentationSchema) { return factory.getAugmentationNodeParser().parse(childNodes, (AugmentationSchema) schema); + } else if (schema instanceof AnyXmlSchemaNode) { + return factory.getAnyXmlNodeParser().parse(childNodes,(AnyXmlSchemaNode)schema); } throw new IllegalArgumentException("Unable to parse node, unknown schema type: " + schema.getClass()); diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/serializer/AnyXmlNodeBaseSerializer.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/serializer/AnyXmlNodeBaseSerializer.java new file mode 100644 index 0000000000..e2fd60bda4 --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/serializer/AnyXmlNodeBaseSerializer.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer; + +import java.util.Collections; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializer; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; + +/** + * Abstract(base) serializer for AnyXmlNodes, serializes elements of type E. + * + * @param type of serialized elements + */ +public abstract class AnyXmlNodeBaseSerializer implements + FromNormalizedNodeSerializer { + + @Override + public final Iterable serialize(AnyXmlSchemaNode schema, AnyXmlNode node) { + return Collections.singletonList(serializeAnyXml(node)); + } + + /** + * + * Serialize the inner value of a AnyXmlNode into element of type E. + * + * @param node to be serialized + * @param schema schema for leaf + * @return serialized inner value as an Element + */ + protected abstract E serializeAnyXml(AnyXmlNode node); +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/serializer/NodeSerializerDispatcher.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/serializer/NodeSerializerDispatcher.java index d08a9fbf8d..c686c31cba 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/serializer/NodeSerializerDispatcher.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/base/serializer/NodeSerializerDispatcher.java @@ -7,7 +7,10 @@ */ package org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; @@ -17,20 +20,20 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; import org.opendaylight.yangtools.yang.data.api.schema.MixinNode; import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializerFactory; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; -import com.google.common.base.Preconditions; -import com.google.common.collect.Iterables; - /** * - * Dispatches the serialization process of nodes according to schema and returns the serialized elements. + * Dispatches the serialization process of nodes according to schema and returns + * the serialized elements. * - * @param type of serialized elements + * @param + * type of serialized elements */ public interface NodeSerializerDispatcher { @@ -38,8 +41,9 @@ public interface NodeSerializerDispatcher { DataContainerChild dataContainerChild); /** - * Abstract implementation that implements the dispatch conditions. Only requires serializers to be provided. - * The same instance of serializer can be provided in case it is immutable. + * Abstract implementation that implements the dispatch conditions. Only + * requires serializers to be provided. The same instance of serializer can + * be provided in case it is immutable. */ public static abstract class BaseNodeSerializerDispatcher implements NodeSerializerDispatcher { private final FromNormalizedNodeSerializerFactory factory; @@ -55,6 +59,8 @@ public interface NodeSerializerDispatcher { return onContainerNode(childSchema, dataContainerChild); } else if (dataContainerChild instanceof LeafNode) { return onLeafNode(childSchema, dataContainerChild); + } else if (dataContainerChild instanceof AnyXmlNode) { + return onAnyXmlNode(childSchema, dataContainerChild); } else if (dataContainerChild instanceof MixinNode) { if (dataContainerChild instanceof LeafSetNode) { return onLeafListNode(childSchema, dataContainerChild); @@ -107,6 +113,15 @@ public interface NodeSerializerDispatcher { return elements; } + private Iterable onAnyXmlNode(Object childSchema, + DataContainerChild dataContainerChild) { + checkSchemaCompatibility(childSchema, AnyXmlSchemaNode.class, dataContainerChild); + Iterable elements = factory.getAnyXmlNodeSerializer().serialize((AnyXmlSchemaNode) childSchema, + (AnyXmlNode) dataContainerChild); + checkOnlyOneSerializedElement(elements, dataContainerChild); + return elements; + } + private static void checkOnlyOneSerializedElement(Iterable elements, DataContainerChild dataContainerChild) { final int size = Iterables.size(elements); diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/dom/parser/DomToNormalizedNodeParserFactory.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/dom/parser/DomToNormalizedNodeParserFactory.java index 53d68f8872..a02b60b2fd 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/dom/parser/DomToNormalizedNodeParserFactory.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/dom/parser/DomToNormalizedNodeParserFactory.java @@ -7,6 +7,7 @@ */ package org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; @@ -19,6 +20,7 @@ import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlCodecProvider; import org.opendaylight.yangtools.yang.data.impl.schema.transform.ToNormalizedNodeParser; import org.opendaylight.yangtools.yang.data.impl.schema.transform.ToNormalizedNodeParserFactory; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser.NodeParserDispatcher; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; @@ -95,4 +97,9 @@ public final class DomToNormalizedNodeParserFactory implements ToNormalizedNodeP public ToNormalizedNodeParser getMapNodeParser() { return mapNodeParser; } + + @Override + public ToNormalizedNodeParser getAnyXmlNodeParser() { + throw new UnsupportedOperationException(); + } } diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/dom/serializer/DomFromNormalizedNodeSerializerFactory.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/dom/serializer/DomFromNormalizedNodeSerializerFactory.java index 0a5aba7c4a..b9d8ef2a75 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/dom/serializer/DomFromNormalizedNodeSerializerFactory.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/transform/dom/serializer/DomFromNormalizedNodeSerializerFactory.java @@ -7,6 +7,7 @@ */ package org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.serializer; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; @@ -19,6 +20,7 @@ import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlCodecProvider; import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializer; import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializerFactory; import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; @@ -98,4 +100,9 @@ public final class DomFromNormalizedNodeSerializerFactory implements FromNormali return mapNodeSerializer; } + @Override + public FromNormalizedNodeSerializer getAnyXmlNodeSerializer() { + throw new UnsupportedOperationException(); + } + } diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/AdditionalConfig/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/AdditionalConfig/pom.xml index 28bb2d9b70..dc2cbd1e67 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/AdditionalConfig/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/AdditionalConfig/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT additional-config @@ -20,7 +20,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} @@ -29,7 +29,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -71,7 +71,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/Correct/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/Correct/pom.xml index 82640e8ee6..d99dd0319a 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/Correct/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/Correct/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT correct @@ -20,7 +20,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} @@ -29,7 +29,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -56,7 +56,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/GenerateTest1/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/GenerateTest1/pom.xml index cd7be965d1..3d931010a3 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/GenerateTest1/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/GenerateTest1/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT generator-test1 @@ -25,7 +25,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -51,7 +51,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/GenerateTest2/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/GenerateTest2/pom.xml index 948c025898..3063698e67 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/GenerateTest2/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/GenerateTest2/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT generator-test2 @@ -24,9 +24,9 @@ org.opendaylight.yangtools generator-test1 - ${it-project.version} + ${project.version} system - ${project.basedir}/../GenerateTest1/target/generator-test1-1.0.jar + ${project.basedir}/../GenerateTest1/target/generator-test1-${project.version}.jar @@ -35,7 +35,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -61,7 +61,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/Generator/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/Generator/pom.xml index be217f8d62..c764eca085 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/Generator/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/Generator/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT generator @@ -21,7 +21,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -48,7 +48,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/InvalidVersion/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/InvalidVersion/pom.xml index 8c45963a8d..2e9982c5dd 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/InvalidVersion/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/InvalidVersion/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT invalid-version @@ -48,7 +48,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -76,7 +76,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/MissingYangInDep/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/MissingYangInDep/pom.xml index 8ade2b1a9c..5641d3ac27 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/MissingYangInDep/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/MissingYangInDep/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT missing-yang-in-dep @@ -21,7 +21,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -58,7 +58,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/NamingConflict/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/NamingConflict/pom.xml index 6fc2c80705..0fae6f7932 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/NamingConflict/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/NamingConflict/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT naming-conflict @@ -21,7 +21,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -46,7 +46,7 @@ org.opendaylight.yangtools maven-sal-api-gen-plugin - ${it-project.version} + ${project.version} jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoGenerators/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoGenerators/pom.xml index d236de3e0a..0a91e17da1 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoGenerators/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoGenerators/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT no-generators @@ -21,13 +21,13 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} generate-sources - + ../files false @@ -49,7 +49,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoOutputDir/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoOutputDir/pom.xml index 20ba31ae92..0ead9af0c6 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoOutputDir/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoOutputDir/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT no-output-dir @@ -21,7 +21,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -48,7 +48,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoYangFiles/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoYangFiles/pom.xml index 3cac79593b..36603f33db 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoYangFiles/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/NoYangFiles/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT no-yang-files @@ -21,7 +21,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -48,7 +48,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/UnknownGenerator/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/UnknownGenerator/pom.xml index 853e8e0c0e..b58576d5ea 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/UnknownGenerator/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/UnknownGenerator/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT unknown-generator @@ -21,7 +21,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} @@ -56,7 +56,7 @@ org.opendaylight.yangtools yang-maven-plugin-spi - ${it-project.version} + ${project.version} test-jar diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/YangRootNotExist/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/YangRootNotExist/pom.xml index 8bb27a854b..1da608feb4 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/YangRootNotExist/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/YangRootNotExist/pom.xml @@ -11,7 +11,7 @@ org.opendaylight.yangtools test-parent - 1.0 + 0.6.2-SNAPSHOT yang-root-not-exist @@ -21,7 +21,7 @@ org.opendaylight.yangtools yang-maven-plugin - ${it-project.version} + ${project.version} diff --git a/yang/yang-maven-plugin-it/src/test/resources/test-parent/pom.xml b/yang/yang-maven-plugin-it/src/test/resources/test-parent/pom.xml index 58085fce5f..18aa62fd4f 100644 --- a/yang/yang-maven-plugin-it/src/test/resources/test-parent/pom.xml +++ b/yang/yang-maven-plugin-it/src/test/resources/test-parent/pom.xml @@ -8,15 +8,17 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + org.opendaylight.yangtools + yangtools-parent + 0.6.2-SNAPSHOT + /../../common/parent/pom.xml + + org.opendaylight.yangtools test-parent - 1.0 pom - - 0.6.2-SNAPSHOT - - additional-config correct diff --git a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/repo/util/AbstractSchemaRepository.java b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/repo/util/AbstractSchemaRepository.java new file mode 100644 index 0000000000..4dd30b90a9 --- /dev/null +++ b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/repo/util/AbstractSchemaRepository.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/eplv10.html + */ +package org.opendaylight.yangtools.yang.model.repo.util; + +import com.google.common.base.Optional; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory; +import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository; +import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceFilter; +import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformationException; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformer; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaTransformerRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AbstractSchemaRepository implements SchemaRepository, SchemaSourceRegistry { + private static final Logger LOG = LoggerFactory.getLogger(AbstractSchemaRepository.class); + private static final Comparator TRANSFORMER_COST_COMPARATOR = new Comparator() { + @Override + public int compare(final SchemaTransformerRegistration o1, final SchemaTransformerRegistration o2) { + return o1.getInstance().getCost() - o2.getInstance().getCost(); + } + }; + + /* + * Output-type -> transformer map. Our usage involves knowing the destination type, + * so we have to work backwards and find a transformer chain which will get us + * to that representation given our available sources. + */ + private final Multimap, SchemaTransformerRegistration> transformers = + HashMultimap.create(); + + /* + * Source identifier -> representation -> provider map. We usually are looking for + * a specific representation a source. + */ + private final Map, AbstractSchemaSourceRegistration>> sources = new HashMap<>(); + + + private static final ListenableFuture> fetchSource(final SourceIdentifier id, final Iterator it) { + if (!it.hasNext()) { + return Futures.immediateFuture(Optional.absent()); + } + + return Futures.transform(((SchemaSourceProvider)it.next().getProvider()).getSource(id), new AsyncFunction, Optional>() { + @Override + public ListenableFuture> apply(final Optional input) throws Exception { + if (input.isPresent()) { + return Futures.immediateFuture(input); + } else { + return fetchSource(id, it); + } + } + }); + } + + private ListenableFuture> transformSchemaSource(final SourceIdentifier id, final Class representation) { + final Multimap, AbstractSchemaSourceRegistration> srcs = sources.get(id); + if (srcs.isEmpty()) { + return Futures.immediateFailedFuture(new SchemaSourceTransformationException( + String.format("No providers producing a representation of %s registered", id))); + } + + final Collection ts = transformers.get(representation); + if (ts.isEmpty()) { + return Futures.immediateFailedFuture(new SchemaSourceTransformationException( + String.format("No transformers producing representation %s registered", representation))); + } + + // Build up the candidate list + final List candidates = new ArrayList<>(); + for (SchemaTransformerRegistration tr : ts) { + final SchemaSourceTransformer t = tr.getInstance(); + final Class i = t.getInputRepresentation(); + if (srcs.containsKey(i)) { + candidates.add(tr); + } else { + LOG.debug("Provider for {} in {} not found, skipping transfomer {}", id, i, t); + } + } + + if (candidates.isEmpty()) { + return Futures.immediateFailedFuture(new SchemaSourceTransformationException( + String.format("No matching source/transformer pair for source %s representation %s found", id, representation))); + } + + Collections.sort(candidates, TRANSFORMER_COST_COMPARATOR); + // return transform(candidates.iterator(), id); + return null; + } + + /** + * Obtain a SchemaSource is selected representation + */ + protected ListenableFuture> getSchemaSource(final SourceIdentifier id, final Class representation) { + final Multimap, AbstractSchemaSourceRegistration> srcs = sources.get(id); + if (srcs == null) { + LOG.debug("No providers registered for source {}", id); + return Futures.immediateFuture(Optional.absent()); + } + + final Collection candidates = srcs.get(representation); + return Futures.transform(AbstractSchemaRepository.fetchSource(id, candidates.iterator()), new AsyncFunction, Optional>() { + @Override + public ListenableFuture> apply(final Optional input) throws Exception { + if (input.isPresent()) { + return Futures.immediateFuture(input); + } + + return transformSchemaSource(id, representation); + } + }); + } + + @Override + public SchemaContextFactory createSchemaContextFactory(final SchemaSourceFilter filter) { + // TODO Auto-generated method stub + return null; + } + + private void addSource(final SourceIdentifier id, final Class rep, final AbstractSchemaSourceRegistration reg) { + Multimap, AbstractSchemaSourceRegistration> m = sources.get(id); + if (m == null) { + m = HashMultimap.create(); + sources.put(id, m); + } + + m.put(rep, reg); + } + + private void removeSource(final SourceIdentifier id, final Class rep, final SchemaSourceRegistration reg) { + final Multimap, AbstractSchemaSourceRegistration> m = sources.get(id); + if (m != null) { + m.remove(rep, reg); + if (m.isEmpty()) { + sources.remove(m); + } + } + } + + @Override + public SchemaSourceRegistration registerSchemaSource( + final SourceIdentifier identifier, final SchemaSourceProvider provider, final Class representation) { + final AbstractSchemaSourceRegistration ret = new AbstractSchemaSourceRegistration(identifier, provider) { + @Override + protected void removeRegistration() { + removeSource(identifier, representation, this); + } + }; + + addSource(identifier, representation, ret); + return ret; + } + + @Override + public SchemaTransformerRegistration registerSchemaSourceTransformer(final SchemaSourceTransformer transformer) { + final SchemaTransformerRegistration ret = new AbstractSchemaTransformerRegistration(transformer) { + @Override + protected void removeRegistration() { + transformers.remove(transformer.getOutputRepresentation(), this); + } + }; + + transformers.put(transformer.getOutputRepresentation(), ret); + return ret; + } +} diff --git a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/repo/util/AbstractSchemaSourceRegistration.java b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/repo/util/AbstractSchemaSourceRegistration.java new file mode 100644 index 0000000000..9ed0afefc7 --- /dev/null +++ b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/repo/util/AbstractSchemaSourceRegistration.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/eplv10.html + */ +package org.opendaylight.yangtools.yang.model.repo.util; + +import com.google.common.base.Preconditions; + +import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration; + +public abstract class AbstractSchemaSourceRegistration extends AbstractObjectRegistration implements SchemaSourceRegistration { + private final SchemaSourceProvider provider; + + protected AbstractSchemaSourceRegistration(final SourceIdentifier identifier, final SchemaSourceProvider provider) { + super(identifier); + this.provider = Preconditions.checkNotNull(provider); + } + + protected SchemaSourceProvider getProvider() { + return provider; + } +} diff --git a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/repo/util/AbstractSchemaTransformerRegistration.java b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/repo/util/AbstractSchemaTransformerRegistration.java new file mode 100644 index 0000000000..d264ae0545 --- /dev/null +++ b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/repo/util/AbstractSchemaTransformerRegistration.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/eplv10.html + */ +package org.opendaylight.yangtools.yang.model.repo.util; + +import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformer; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaTransformerRegistration; + +public abstract class AbstractSchemaTransformerRegistration extends AbstractObjectRegistration> implements SchemaTransformerRegistration { + protected AbstractSchemaTransformerRegistration( + final SchemaSourceTransformer transformer) { + super(transformer); + } +} diff --git a/yang/yang-parser-impl/src/main/antlr/YangLexer.g4 b/yang/yang-parser-impl/src/main/antlr/YangLexer.g4 index f4fb777ccd..ac9358c72e 100644 --- a/yang/yang-parser-impl/src/main/antlr/YangLexer.g4 +++ b/yang/yang-parser-impl/src/main/antlr/YangLexer.g4 @@ -108,7 +108,7 @@ END_IDENTIFIER_LEFT_BRACE : '{' ->type(LEFT_BRACE), popMode; fragment SUB_STRING : ('"' (ESC | ~["])*'"') | ('\'' (ESC | ~['])*'\'') ; -STRING: ((~( '\r' | '\n' | '\t' | ' ' | ';' | '{' | '"' | '\'' )+) | SUB_STRING ) ->popMode;// IDENTIFIER ; +STRING: ((~( '\r' | '\n' | '\t' | ' ' | ';' | '{' | '"' | '\'')~( '\r' | '\n' | '\t' | ' ' | ';' | '{' )* ) | SUB_STRING ) ->popMode;// IDENTIFIER ; S : [ \n\r\t] -> skip; mode BLOCK_COMMENT_MODE; diff --git a/yang/yang-parser-impl/src/main/antlr/YangParser.g4 b/yang/yang-parser-impl/src/main/antlr/YangParser.g4 index 13f5dbba5e..c7054fbc9f 100644 --- a/yang/yang-parser-impl/src/main/antlr/YangParser.g4 +++ b/yang/yang-parser-impl/src/main/antlr/YangParser.g4 @@ -29,9 +29,9 @@ unknown_statement : (YIN_ELEMENT_KEYWORD | YANG_VERSION_KEYWORD | WHEN_KEYWORD | NAMESPACE_KEYWORD | MUST_KEYWORD | MODULE_KEYWORD | MIN_ELEMENTS_KEYWORD | MAX_ELEMENTS_KEYWORD | MANDATORY_KEYWORD | LIST_KEYWORD | LENGTH_KEYWORD | LEAF_LIST_KEYWORD | LEAF_KEYWORD | KEY_KEYWORD | INPUT_KEYWORD | INCLUDE_KEYWORD | IMPORT_KEYWORD | IF_FEATURE_KEYWORD | IDENTITY_KEYWORD | GROUPING_KEYWORD | FRACTION_DIGITS_KEYWORD | FEATURE_KEYWORD | DEVIATE_KEYWORD | DEVIATION_KEYWORD | EXTENSION_KEYWORD | - ERROR_MESSAGE_KEYWORD | ERROR_APP_TAG_KEYWORD | ENUM_KEYWORD | DESCRIPTION_KEYWORD | DEFAULT_KEYWORD | CONTAINER_KEYWORD | CONTACT_KEYWORD | + ERROR_MESSAGE_KEYWORD | ERROR_APP_TAG_KEYWORD | ENUM_KEYWORD | DESCRIPTION_KEYWORD | STATUS_KEYWORD | DEFAULT_KEYWORD | CONTAINER_KEYWORD | CONTACT_KEYWORD | CONFIG_KEYWORD | CHOICE_KEYWORD | CASE_KEYWORD | BIT_KEYWORD | BELONGS_TO_KEYWORD | BASE_KEYWORD | AUGMENT_KEYWORD | - ANYXML_KEYWORD | IDENTIFIER) string? (SEMICOLON | (LEFT_BRACE unknown_statement* | identifier_stmt* RIGHT_BRACE)*); + ANYXML_KEYWORD | IDENTIFIER) string? (SEMICOLON | (LEFT_BRACE (unknown_statement | identifier_stmt)* RIGHT_BRACE)*); stmtend : (SEMICOLON) | (LEFT_BRACE identifier_stmt? RIGHT_BRACE); deviate_replace_stmt : DEVIATE_KEYWORD string /* REPLACE_KEYWORD */ (SEMICOLON | (LEFT_BRACE (identifier_stmt |type_stmt | units_stmt | default_stmt | config_stmt | mandatory_stmt | min_elements_stmt | max_elements_stmt )* RIGHT_BRACE)); @@ -63,11 +63,11 @@ short_case_stmt : container_stmt | leaf_stmt | leaf_list_stmt | list_stmt | anyx choice_stmt : CHOICE_KEYWORD string (SEMICOLON | (LEFT_BRACE (identifier_stmt |when_stmt | if_feature_stmt | default_stmt | config_stmt | mandatory_stmt | status_stmt | description_stmt | reference_stmt | short_case_stmt | case_stmt)* RIGHT_BRACE)); unique_stmt : UNIQUE_KEYWORD string stmtend; key_stmt : KEY_KEYWORD string stmtend; -list_stmt : LIST_KEYWORD string LEFT_BRACE (identifier_stmt |when_stmt | if_feature_stmt | must_stmt | key_stmt | unique_stmt | config_stmt | min_elements_stmt | max_elements_stmt | ordered_by_stmt | status_stmt | description_stmt | reference_stmt | typedef_stmt | grouping_stmt | data_def_stmt )* RIGHT_BRACE; +list_stmt : LIST_KEYWORD string LEFT_BRACE (when_stmt | if_feature_stmt | must_stmt | key_stmt | unique_stmt | config_stmt | min_elements_stmt | max_elements_stmt | ordered_by_stmt | status_stmt | description_stmt | reference_stmt | typedef_stmt | grouping_stmt | data_def_stmt | identifier_stmt)* RIGHT_BRACE; leaf_list_stmt : LEAF_LIST_KEYWORD string LEFT_BRACE (identifier_stmt |when_stmt | if_feature_stmt | type_stmt | units_stmt | must_stmt | config_stmt | min_elements_stmt | max_elements_stmt | ordered_by_stmt | status_stmt | description_stmt | reference_stmt )* RIGHT_BRACE; leaf_stmt : LEAF_KEYWORD string LEFT_BRACE (identifier_stmt |when_stmt | if_feature_stmt | type_stmt | units_stmt | must_stmt | default_stmt | config_stmt | mandatory_stmt | status_stmt | description_stmt | reference_stmt )* RIGHT_BRACE; -container_stmt : CONTAINER_KEYWORD string (SEMICOLON | (LEFT_BRACE (identifier_stmt | when_stmt | if_feature_stmt | must_stmt | presence_stmt | config_stmt | status_stmt | description_stmt | reference_stmt | typedef_stmt | grouping_stmt | data_def_stmt )* RIGHT_BRACE)); -grouping_stmt : GROUPING_KEYWORD string (SEMICOLON | (LEFT_BRACE (identifier_stmt |status_stmt | description_stmt | reference_stmt | typedef_stmt | grouping_stmt | data_def_stmt )* RIGHT_BRACE)); +container_stmt : CONTAINER_KEYWORD string (SEMICOLON | (LEFT_BRACE (when_stmt | if_feature_stmt | must_stmt | presence_stmt | config_stmt | status_stmt | description_stmt | reference_stmt | typedef_stmt | grouping_stmt | data_def_stmt | identifier_stmt)* RIGHT_BRACE)); +grouping_stmt : GROUPING_KEYWORD string (SEMICOLON | (LEFT_BRACE (status_stmt | description_stmt | reference_stmt | typedef_stmt | grouping_stmt | data_def_stmt | identifier_stmt)* RIGHT_BRACE)); value_stmt : VALUE_KEYWORD string stmtend; max_value_arg : /*UNBOUNDED_KEYWORD |*/ string; min_value_arg : /*UNBOUNDED_KEYWORD |*/ string; diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/ParserListenerUtils.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/ParserListenerUtils.java index 2757c10e1e..542506045d 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/ParserListenerUtils.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/ParserListenerUtils.java @@ -1069,10 +1069,12 @@ public final class ParserListenerUtils { if (parent instanceof TypeDefinitionBuilder) { TypeDefinitionBuilder typedef = (TypeDefinitionBuilder) parent; - typedef.setRanges(rangeStatements); - typedef.setLengths(lengthStatements); - typedef.setPatterns(patternStatements); - typedef.setFractionDigits(fractionDigits); + if (!(typedef instanceof UnionTypeBuilder)) { + typedef.setRanges(rangeStatements); + typedef.setLengths(lengthStatements); + typedef.setPatterns(patternStatements); + typedef.setFractionDigits(fractionDigits); + } return unknownType.build(); } else { TypeDefinition baseType = unknownType.build(); diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangModelBasicValidationListener.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangModelBasicValidationListener.java index 5805cd7e8c..133c7b18a7 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangModelBasicValidationListener.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangModelBasicValidationListener.java @@ -8,10 +8,12 @@ package org.opendaylight.yangtools.yang.parser.impl; import com.google.common.collect.Sets; + import java.net.URI; import java.net.URISyntaxException; import java.util.HashSet; import java.util.Set; + import org.antlr.v4.runtime.tree.ParseTree; import org.opendaylight.yangtools.antlrv4.code.gen.YangParser; import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Anyxml_stmtContext; @@ -66,7 +68,7 @@ import org.slf4j.LoggerFactory; * This validator expects only one module or submodule per file and performs * only basic validation where context from all yang models is not present. */ -final class YangModelBasicValidationListener extends YangParserBaseListener { +public final class YangModelBasicValidationListener extends YangParserBaseListener { private static final Logger LOGGER = LoggerFactory.getLogger(YangModelBasicValidationListener.class); private final Set uniquePrefixes = new HashSet<>(); private final Set uniqueImports = new HashSet<>(); diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangParserImpl.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangParserImpl.java index 0002c11187..1d0d48a7dd 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangParserImpl.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangParserImpl.java @@ -22,6 +22,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.collect.HashBiMap; import com.google.common.io.ByteSource; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -37,7 +38,9 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; + import javax.annotation.concurrent.Immutable; + import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; @@ -107,8 +110,7 @@ public final class YangParserImpl implements YangContextParser { } @Override - public SchemaContext parseFile(final File yangFile, final File directory) throws IOException, - YangSyntaxErrorException { + public SchemaContext parseFile(final File yangFile, final File directory) throws IOException, YangSyntaxErrorException { Preconditions.checkState(yangFile.exists(), yangFile + " does not exists"); Preconditions.checkState(directory.exists(), directory + " does not exists"); Preconditions.checkState(directory.isDirectory(), directory + " is not a directory"); @@ -194,12 +196,8 @@ public final class YangParserImpl implements YangContextParser { } @Override - public SchemaContext parseSources(final Collection sources) throws IOException, - YangSyntaxErrorException { - Collection unsorted = parseYangModelSources(sources).values(); - Set sorted = new LinkedHashSet<>( - ModuleDependencySort.sort(unsorted.toArray(new Module[unsorted.size()]))); - return resolveSchemaContext(sorted); + public SchemaContext parseSources(final Collection sources) throws IOException,YangSyntaxErrorException { + return assembleContext(parseYangModelSources(sources).values()); } @Override @@ -236,7 +234,7 @@ public final class YangParserImpl implements YangContextParser { return resolveSchemaContext(result); } - private LinkedHashMap> resolveModulesWithImports(final List sorted, + private static LinkedHashMap> resolveModulesWithImports(final List sorted, final SchemaContext context) { final LinkedHashMap> modules = orderModules(sorted); for (ModuleBuilder module : sorted) { @@ -335,8 +333,21 @@ public final class YangParserImpl implements YangContextParser { return new SchemaContextImpl(modules, identifiersToSources); } - private Map parseYangModelSources(final Collection sources) throws IOException, - YangSyntaxErrorException { + public Collection buildModules(final Collection builders) { + List sorted = ModuleDependencySort.sort(builders); + Map> modules = resolveModulesWithImports(sorted, null); + Map builderToModule = build(modules); + + return builderToModule.values(); + } + + public SchemaContext assembleContext(final Collection modules) { + final Set sorted = new LinkedHashSet<>( + ModuleDependencySort.sort(modules.toArray(new Module[modules.size()]))); + return resolveSchemaContext(sorted); + } + + private Map parseYangModelSources(final Collection sources) throws IOException, YangSyntaxErrorException { if (sources == null || sources.isEmpty()) { return Collections.emptyMap(); } @@ -368,8 +379,7 @@ public final class YangParserImpl implements YangContextParser { * @throws YangSyntaxErrorException */ // TODO: remove ByteSource result after removing YangModelParser - private Map resolveSources(final Collection streams) throws IOException, - YangSyntaxErrorException { + private Map resolveSources(final Collection streams) throws IOException, YangSyntaxErrorException { Map builders = parseSourcesToBuilders(streams); return resolveSubmodules(builders); } @@ -511,7 +521,7 @@ public final class YangParserImpl implements YangContextParser { * topologically sorted modules * @return modules ordered by name and revision */ - private LinkedHashMap> orderModules(final List modules) { + private static LinkedHashMap> orderModules(final List modules) { final LinkedHashMap> result = new LinkedHashMap<>(); for (final ModuleBuilder builder : modules) { if (builder == null) { @@ -583,31 +593,29 @@ public final class YangParserImpl implements YangContextParser { } } - private Map parseYangSources(final Collection sources) throws IOException, - YangSyntaxErrorException { + private Map parseYangSources(final Collection sources) throws IOException, YangSyntaxErrorException { final Map trees = new HashMap<>(); for (ByteSource source : sources) { - trees.put(source, parseYangSource(source)); + try (InputStream stream = source.openStream()) { + trees.put(source, parseYangSource(stream)); + } } return trees; } - private YangContext parseYangSource(final ByteSource source) throws IOException, YangSyntaxErrorException { - try (InputStream stream = source.openStream()) { - final ANTLRInputStream input = new ANTLRInputStream(stream); - final YangLexer lexer = new YangLexer(input); - final CommonTokenStream tokens = new CommonTokenStream(lexer); - final YangParser parser = new YangParser(tokens); - parser.removeErrorListeners(); + public static YangContext parseYangSource(final InputStream stream) throws IOException, YangSyntaxErrorException { + final YangLexer lexer = new YangLexer(new ANTLRInputStream(stream)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + final YangParser parser = new YangParser(tokens); + parser.removeErrorListeners(); - final YangErrorListener errorListener = new YangErrorListener(); - parser.addErrorListener(errorListener); + final YangErrorListener errorListener = new YangErrorListener(); + parser.addErrorListener(errorListener); - final YangContext result = parser.yang(); - errorListener.validate(); + final YangContext result = parser.yang(); + errorListener.validate(); - return result; - } + return result; } /** @@ -1276,5 +1284,4 @@ public final class YangParserImpl implements YangContextParser { } dev.setTargetPath(((SchemaNodeBuilder) currentParent).getPath()); } - } diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/util/YangModelDependencyInfo.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/util/YangModelDependencyInfo.java index fcf0b8924c..97590779b2 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/util/YangModelDependencyInfo.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/util/YangModelDependencyInfo.java @@ -11,9 +11,12 @@ import static org.opendaylight.yangtools.yang.parser.impl.ParserListenerUtils.ge import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; + import java.io.InputStream; import java.util.Date; import java.util.List; + +import org.antlr.v4.runtime.ParserRuleContext; import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Belongs_to_stmtContext; import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Import_stmtContext; import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Include_stmtContext; @@ -25,6 +28,7 @@ import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Submodule_stmtCont import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.YangContext; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.model.api.ModuleImport; +import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException; import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; /** @@ -53,7 +57,7 @@ public abstract class YangModelDependencyInfo { private final ImmutableSet dependencies; YangModelDependencyInfo(final String name, final String formattedRevision, - final ImmutableSet imports, final ImmutableSet includes) { + final ImmutableSet imports, final ImmutableSet includes) { this.name = name; this.formattedRevision = formattedRevision; this.revision = QName.parseRevision(formattedRevision); @@ -142,6 +146,29 @@ public abstract class YangModelDependencyInfo { return true; } + /** + * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree + * of a YANG model. + * + * @param tree Abstract syntax tree + * @return {@link YangModelDependencyInfo} + * @throws YangSyntaxErrorException + * If the AST is not a valid YANG module/submodule + */ + public static YangModelDependencyInfo fromAST(final String name, final ParserRuleContext tree) throws YangSyntaxErrorException { + final Optional moduleCtx = getFirstContext(tree, Module_stmtContext.class); + if (moduleCtx.isPresent()) { + return parseModuleContext(moduleCtx.get()); + } + + final Optional submoduleCtx = getFirstContext(tree, Submodule_stmtContext.class); + if (submoduleCtx.isPresent()) { + return parseSubmoduleContext(submoduleCtx.get()); + } + + throw new YangSyntaxErrorException(name, 0, 0, "Unknown YANG text type"); + } + /** * Extracts {@link YangModelDependencyInfo} from input stream * containing YANG model. diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java new file mode 100644 index 0000000000..45ad366285 --- /dev/null +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.parser.repo; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +import org.opendaylight.yangtools.yang.model.api.ModuleImport; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.parser.impl.util.YangModelDependencyInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Inter-module dependency resolved. Given a set of schema source identifiers and their + * corresponding dependency information, the {@link #create(Map)} method creates a + * a view of how consistent the dependencies are. In particular, this detects whether + * any imports are unsatisfied. + * + * FIXME: improve this class to track and expose how wildcard imports were resolved. + * That information will allow us to track "damage" to dependency resolution + * as new models are added to a schema context. + */ +final class DependencyResolver { + private static final Logger LOG = LoggerFactory.getLogger(DependencyResolver.class); + private final Collection resolvedSources; + private final Collection unresolvedSources; + private final Multimap unsatisfiedImports; + + public DependencyResolver(final Collection resolvedSources, + final Collection unresolvedSources, final Multimap unsatisfiedImports) { + this.resolvedSources = Preconditions.checkNotNull(resolvedSources); + this.unresolvedSources = Preconditions.checkNotNull(unresolvedSources); + this.unsatisfiedImports = Preconditions.checkNotNull(unsatisfiedImports); + } + + private static SourceIdentifier findWildcard(final Iterable haystack, final String needle) { + for (SourceIdentifier r : haystack) { + if (r.getName().equals(needle)) { + return r; + } + } + + return null; + } + + private static boolean isKnown(final Collection haystack, final ModuleImport mi) { + final String rev = mi.getRevision() != null ? mi.getRevision().toString() : null; + final SourceIdentifier msi = SourceIdentifier.create(mi.getModuleName(), Optional.fromNullable(rev)); + + // Quick lookup + if (haystack.contains(msi)) { + return true; + } + + // Slow revision-less walk + return rev == null && findWildcard(haystack, mi.getModuleName()) != null; + } + + public static final DependencyResolver create(final Map depInfo) { + final Collection resolved = new ArrayList<>(depInfo.size()); + final Collection pending = new ArrayList<>(depInfo.keySet()); + + boolean progress; + do { + progress = false; + + final Iterator it = pending.iterator(); + while (it.hasNext()) { + final SourceIdentifier id = it.next(); + final YangModelDependencyInfo dep = depInfo.get(id); + + boolean okay = true; + for (ModuleImport mi : dep.getDependencies()) { + if (!isKnown(resolved, mi)) { + LOG.debug("Source {} is missing import {}", id, mi); + okay = false; + break; + } + } + + if (okay) { + LOG.debug("Resolved source {}", id); + resolved.add(id); + it.remove(); + progress = true; + } + } + } while (progress); + + if (!pending.isEmpty()) { + final Multimap imports = ArrayListMultimap.create(); + for (SourceIdentifier id : pending) { + final YangModelDependencyInfo dep = depInfo.get(id); + for (ModuleImport mi : dep.getDependencies()) { + if (!isKnown(pending, mi) && !isKnown(resolved, mi)) { + imports.put(id, mi); + } + } + } + + return new DependencyResolver(resolved, pending, imports); + } else { + return new DependencyResolver(resolved, Collections.emptyList(), ImmutableMultimap.of()); + } + } + + /** + * Collection of sources which have been resolved. + * + * @return + */ + Collection getResolvedSources() { + return resolvedSources; + } + + /** + * Collection of sources which have not been resolved due to missing dependencies. + * + * @return + */ + Collection getUnresolvedSources() { + return unresolvedSources; + } + + /** + * Detailed information about which imports were missing. The key in the map + * is the source identifier of module which was issuing an import, the values + * are imports which were unsatisfied. + * + * Note that this map contains only imports which are missing from the reactor, + * not transitive failures. + * + * Examples: + * + * If A imports B, B imports C, and both A and B are in the reactor, only B->C + * will be reported. + * + * If A imports B and C, B imports C, and both A and B are in the reactor, + * A->C and B->C will be reported. + * + * @return + */ + Multimap getUnsatisfiedImports() { + return unsatisfiedImports; + } +} diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/URLSchemaContextResolver.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/URLSchemaContextResolver.java new file mode 100644 index 0000000000..8ee18e58a5 --- /dev/null +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/URLSchemaContextResolver.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.parser.repo; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Function; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Maps.EntryTransformer; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; +import org.opendaylight.yangtools.concepts.ObjectRegistration; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformationException; +import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; +import org.opendaylight.yangtools.yang.parser.impl.YangParserListenerImpl; +import org.opendaylight.yangtools.yang.parser.impl.util.YangModelDependencyInfo; +import org.opendaylight.yangtools.yang.parser.util.ASTSchemaSource; +import org.opendaylight.yangtools.yang.parser.util.TextToASTTransformer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ThreadSafe +public class URLSchemaContextResolver { + private static final Logger LOG = LoggerFactory.getLogger(URLSchemaContextResolver.class); + private static final Function EXTRACT_DEPINFO = new Function() { + @Override + public YangModelDependencyInfo apply(final ASTSchemaSource input) { + return input.getDependencyInformation(); + } + }; + private static final EntryTransformer, YangModelDependencyInfo> SQUASH_DEPINFO = + new EntryTransformer, YangModelDependencyInfo>() { + @Override + public YangModelDependencyInfo transformEntry(final SourceIdentifier key, final Collection value) { + // FIXME: validate that all the info objects are the same + return value.iterator().next(); + } + }; + private static final Function EXTRACT_AST = new Function() { + @Override + public ParserRuleContext apply(final ASTSchemaSource input) { + return input.getAST(); + } + }; + private static final EntryTransformer, ParserRuleContext> SQUASH_AST = + new EntryTransformer, ParserRuleContext>() { + @Override + public ParserRuleContext transformEntry(final SourceIdentifier key, final Collection value) { + // FIXME: validate that all the info objects are the same + return value.iterator().next(); + } + }; + + @GuardedBy("this") + private final Multimap resolvedRegs = ArrayListMultimap.create(); + private final AtomicReference> currentSchemaContext = new AtomicReference<>(Optional.absent()); + private final Queue outstandingRegs = new ConcurrentLinkedQueue<>(); + private final TextToASTTransformer transformer; + @GuardedBy("this") + private Object version = new Object(); + @GuardedBy("this") + private Object contextVersion = version; + + private final class URLRegistration extends AbstractObjectRegistration { + @GuardedBy("this") + private CheckedFuture future; + @GuardedBy("this") + private ASTSchemaSource result; + + protected URLRegistration(final URL url, final CheckedFuture future) { + super(url); + this.future = Preconditions.checkNotNull(future); + } + + private synchronized boolean setResult(final ASTSchemaSource result) { + if (future != null) { + this.result = result; + return true; + } else { + return false; + } + } + + @Override + protected void removeRegistration() { + // Cancel the future, but it may already be completing + future.cancel(false); + + synchronized (this) { + future = null; + outstandingRegs.remove(this); + if (result != null) { + removeSchemaSource(result); + } + } + } + } + + private URLSchemaContextResolver(final TextToASTTransformer transformer) { + this.transformer = Preconditions.checkNotNull(transformer); + } + + public static URLSchemaContextResolver create(final String name) { + final ThreadFactory f = new ThreadFactoryBuilder().setDaemon(true).setNameFormat(name + "yangparser-%d").build(); + final ListeningExecutorService s = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(f)); + + return new URLSchemaContextResolver(TextToASTTransformer.create(s)); + } + + /** + * Register a URL hosting a YANG Text file. + * + * @param url URL + */ + public ObjectRegistration registerSource(final URL url) { + checkArgument(url != null, "Supplied URL must not be null"); + + final SourceIdentifier id = SourceIdentifier.create(url.getFile().toString(), Optional.absent()); + final YangTextSchemaSource text = new YangTextSchemaSource(id) { + @Override + public InputStream openStream() throws IOException { + return url.openStream(); + } + + @Override + protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) { + return toStringHelper.add("url", url); + } + }; + + final CheckedFuture ast = transformer.transformSchemaSource(text); + final URLRegistration reg = new URLRegistration(url, ast); + outstandingRegs.add(reg); + + Futures.addCallback(ast, new FutureCallback() { + @Override + public void onSuccess(final ASTSchemaSource result) { + LOG.trace("Resolved URL {} to source {}", url, result); + + outstandingRegs.remove(reg); + if (reg.setResult(result)) { + addSchemaSource(result); + } + } + + @Override + public void onFailure(final Throwable t) { + LOG.warn("Failed to parse YANG text from {}, ignoring it", url, t); + outstandingRegs.remove(reg); + } + }); + + return reg; + } + + private synchronized void addSchemaSource(final ASTSchemaSource src) { + resolvedRegs.put(src.getIdentifier(), src); + version = new Object(); + } + + private synchronized void removeSchemaSource(final ASTSchemaSource src) { + resolvedRegs.put(src.getIdentifier(), src); + version = new Object(); + } + + /** + * Try to parse all currently available yang files and build new schema context. + * @return new schema context iif there is at least 1 yang file registered and + * new schema context was successfully built. + */ + public Optional getSchemaContext() { + while (true) { + Optional result; + final Multimap sources; + final Object v; + synchronized (this) { + result = currentSchemaContext.get(); + if (version == contextVersion) { + return result; + } + + sources = ImmutableMultimap.copyOf(resolvedRegs); + v = version; + } + + if (!sources.isEmpty()) { + final Map deps = + Maps.transformEntries(Multimaps.transformValues(sources, EXTRACT_DEPINFO).asMap(), SQUASH_DEPINFO); + + LOG.debug("Resolving dependency reactor {}", deps); + final DependencyResolver res = DependencyResolver.create(deps); + if (!res.getUnresolvedSources().isEmpty()) { + LOG.debug("Omitting models {} due to unsatisfied imports {}", res.getUnresolvedSources(), res.getUnsatisfiedImports()); + } + + final Map asts = + Maps.transformEntries(Multimaps.transformValues(sources, EXTRACT_AST).asMap(), SQUASH_AST); + + final ParseTreeWalker walker = new ParseTreeWalker(); + final Map sourceToBuilder = new LinkedHashMap<>(); + + for (Entry entry : asts.entrySet()) { + final YangParserListenerImpl yangModelParser = new YangParserListenerImpl(entry.getKey().getName()); + walker.walk(yangModelParser, entry.getValue()); + ModuleBuilder moduleBuilder = yangModelParser.getModuleBuilder(); + + moduleBuilder.setSource(sources.get(entry.getKey()).iterator().next().getYangText()); + sourceToBuilder.put(entry.getKey(), moduleBuilder); + } + LOG.debug("Modules ready for integration"); + + final YangParserImpl parser = YangParserImpl.getInstance(); + final Collection modules = parser.buildModules(sourceToBuilder.values()); + LOG.debug("Integrated cross-references modules"); + + result = Optional.of(parser.assembleContext(modules)); + } else { + result = Optional.absent(); + } + + synchronized (this) { + if (v == version) { + currentSchemaContext.set(result); + contextVersion = version; + return result; + } + + LOG.debug("Context version {} expected {}, retry", version, v); + } + } + } +} diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/util/ASTSchemaSource.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/util/ASTSchemaSource.java new file mode 100644 index 0000000000..ab353a04ad --- /dev/null +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/util/ASTSchemaSource.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.parser.util; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; + +import javax.annotation.Nonnull; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException; +import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.parser.impl.util.YangModelDependencyInfo; + +/** + * Abstract Syntax Tree representation of a schema source. This representation + * is internal to the YANG parser implementation, as it relies on ANTLR types. + * + * Instances of this representation are used for caching purposes, as they + * are a natural intermediate step in YANG text processing pipeline: the text + * has been successfully parsed, so we know it is syntactically correct. It also + * passes basic semantic validation and we were able to extract dependency + * information. + */ +public final class ASTSchemaSource implements SchemaSourceRepresentation { + private final YangModelDependencyInfo depInfo; + private final ParserRuleContext tree; + private final SourceIdentifier id; + private final String text; + + private ASTSchemaSource(final @Nonnull SourceIdentifier id, @Nonnull final ParserRuleContext tree, final @Nonnull YangModelDependencyInfo depInfo, final @Nonnull String text) { + this.depInfo = Preconditions.checkNotNull(depInfo); + this.tree = Preconditions.checkNotNull(tree); + this.id = Preconditions.checkNotNull(id); + this.text = Preconditions.checkNotNull(text); + } + + /** + * Create a new instance of AST representation for a abstract syntax tree, + * performing minimal semantic analysis to acquire dependency information. + * + * @param name YANG source name. Used only for error reporting. + * @param tree ANTLR abstract syntax tree + * @return A new representation instance. + * @throws YangSyntaxErrorException if we fail to extract dependency information. + */ + public static final ASTSchemaSource create(final @Nonnull String name, final @Nonnull ParserRuleContext tree) throws YangSyntaxErrorException { + final YangModelDependencyInfo depInfo = YangModelDependencyInfo.fromAST(name, tree); + final SourceIdentifier id = new SourceIdentifier(depInfo.getName(), Optional.of(depInfo.getFormattedRevision())); + return new ASTSchemaSource(id, tree, depInfo, null); + } + + /** + * Create a new instance of AST representation for a abstract syntax tree, + * performing minimal semantic analysis to acquire dependency information. + * + * @param name YANG source name. Used only for error reporting. + * @param tree ANTLR abstract syntax tree + * @return A new representation instance. + * @throws YangSyntaxErrorException if we fail to extract dependency information. + * + * @deprecated Migration only, will be removed as soon as the migration is completed. + */ + @Deprecated + public static final ASTSchemaSource create(final @Nonnull String name, final @Nonnull ParserRuleContext tree, final String text) throws YangSyntaxErrorException { + final YangModelDependencyInfo depInfo = YangModelDependencyInfo.fromAST(name, tree); + final SourceIdentifier id = new SourceIdentifier(depInfo.getName(), Optional.of(depInfo.getFormattedRevision())); + return new ASTSchemaSource(id, tree, depInfo, text); + } + + + @Override + public SourceIdentifier getIdentifier() { + return id; + } + + @Override + public Class getType() { + return ASTSchemaSource.class; + } + + /** + * Return the underlying abstract syntax tree. + * + * @return Underlying AST. + */ + public @Nonnull ParserRuleContext getAST() { + return tree; + } + + /** + * Return the dependency information as extracted from the AST. + * + * FIXME: this method should be extracted into a public interface in the + * model.api.repo class, relying solely on model.api types. + * + * @return Dependency information. + */ + public @Nonnull YangModelDependencyInfo getDependencyInformation() { + return depInfo; + } + + /** + * Return the semantically-equivalent text YANG text source. + * + * @return YANG text source + * @deprecated Used for migration purposes. Users are advised to use the + * schema repository to acquire the representation of their + * choice. Will be removed as soon as the migration is completed. + */ + @Deprecated + public @Nonnull String getYangText() { + return text; + } +} diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/util/TextToASTTransformer.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/util/TextToASTTransformer.java new file mode 100644 index 0000000000..ce9d6a8ce9 --- /dev/null +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/util/TextToASTTransformer.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.parser.util; + +import com.google.common.base.Charsets; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.io.CharStreams; +import com.google.common.io.InputSupplier; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListeningExecutorService; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.Callable; + +import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.YangContext; +import org.opendaylight.yangtools.util.concurrent.ExceptionMapper; +import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException; +import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformationException; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformer; +import org.opendaylight.yangtools.yang.parser.impl.YangModelBasicValidationListener; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A {@link SchemaSourceTransformer} which handles translation of models from + * {@link YangTextSchemaSource} representation into {@link ASTSchemaSource}. + * + * While this class is currently used explicitly, its long-term purpose is to + * be registered with a {@link org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry} + * and be invoked on demand when the processing pipeline requests the + * ASTSchemaSource representation. + */ +public final class TextToASTTransformer implements SchemaSourceTransformer { + private static final Logger LOG = LoggerFactory.getLogger(TextToASTTransformer.class); + private static final Function MAPPER = new ExceptionMapper("Source transformation", SchemaSourceTransformationException.class) { + @Override + protected SchemaSourceTransformationException newWithCause(final String message, final Throwable cause) { + return new SchemaSourceTransformationException(message, cause); + } + }; + + private final ListeningExecutorService executor; + + private TextToASTTransformer(final ListeningExecutorService executor) { + this.executor = Preconditions.checkNotNull(executor); + } + + public static final TextToASTTransformer create(final ListeningExecutorService executor) { + return new TextToASTTransformer(executor); + } + + @Override + public Class getInputRepresentation() { + return YangTextSchemaSource.class; + } + + @Override + public Class getOutputRepresentation() { + return ASTSchemaSource.class; + } + + @Override + public CheckedFuture transformSchemaSource(final YangTextSchemaSource source) { + return Futures.makeChecked(executor.submit(new Callable() { + @Override + public ASTSchemaSource call() throws IOException, YangSyntaxErrorException { + try (InputStream is = source.openStream()) { + final YangContext ctx = YangParserImpl.parseYangSource(is); + LOG.debug("Model {} parsed successfully", source); + + final ParseTreeWalker walker = new ParseTreeWalker(); + final YangModelBasicValidationListener validator = new YangModelBasicValidationListener(); + walker.walk(validator, ctx); + LOG.debug("Model {} validated successfully", source); + + // Backwards compatibility + final String text = CharStreams.toString( + CharStreams.newReaderSupplier(new InputSupplier() { + @Override + public InputStream getInput() throws IOException { + return source.openStream(); + } + }, Charsets.UTF_8)); + + return ASTSchemaSource.create(source.getIdentifier().getName(), ctx, text); + } + } + }), MAPPER); + } + + @Override + public int getCost() { + // We perform a direct translation, so the cost is 1. + return 1; + } +} diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/impl/TypesResolutionTest.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/impl/TypesResolutionTest.java index 050f620928..022cda45a5 100644 --- a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/impl/TypesResolutionTest.java +++ b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/impl/TypesResolutionTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.math.BigInteger; import java.net.URI; +import java.util.Arrays; import java.util.List; import java.util.Set; import org.junit.Before; @@ -333,4 +334,13 @@ public class TypesResolutionTest { assertNotNull(type); } + @Test + public void testUnionWithExt() throws Exception { + File extdef = new File(getClass().getResource("/types/union-with-ext/extdef.yang").toURI()); + File unionbug = new File(getClass().getResource("/types/union-with-ext/unionbug.yang").toURI()); + File inet = new File(getClass().getResource("/ietf/ietf-inet-types@2010-09-24.yang").toURI()); + YangContextParser parser = new YangParserImpl(); + parser.parseFiles(Arrays.asList(extdef, unionbug, inet)); + } + } diff --git a/yang/yang-parser-impl/src/test/resources/types/union-with-ext/extdef.yang b/yang/yang-parser-impl/src/test/resources/types/union-with-ext/extdef.yang new file mode 100644 index 0000000000..68a568bfb9 --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/types/union-with-ext/extdef.yang @@ -0,0 +1,13 @@ +module "extdef" { + yang-version 1; + namespace "urn:test:bug:extdef"; + prefix "extdef"; + + revision 2012-04-16 { + } + + extension "help" { + argument "text"; + } + +} diff --git a/yang/yang-parser-impl/src/test/resources/types/union-with-ext/unionbug.yang b/yang/yang-parser-impl/src/test/resources/types/union-with-ext/unionbug.yang new file mode 100644 index 0000000000..a8821683d9 --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/types/union-with-ext/unionbug.yang @@ -0,0 +1,31 @@ +module unionbug { + yang-version 1; + namespace "urn:test:bug:unionbug"; + prefix "unionbug"; + + import extdef { + prefix extdef; + } + + import ietf-inet-types { + prefix "inet"; + } + + revision 2012-04-16 { + } + + typedef address { + type union { + type inet:ip-address { + extdef:help "IP address"; + } + type inet:ip-prefix { + extdef:help "Subnet"; + } + type string { + extdef:help "Address name"; + } + } + } + +}