Binding2 runtime - Codecs impl #1 32/58332/2
authorMartin Ciglan <martin.ciglan@pantheon.tech>
Mon, 5 Jun 2017 09:02:30 +0000 (11:02 +0200)
committerMartin Ciglan <martin.ciglan@pantheon.tech>
Tue, 6 Jun 2017 22:31:21 +0000 (22:31 +0000)
- value based codecs & relatives

TODO: more Javadocs, test coverage

Change-Id: I1c780772ea93d613c4e220ded61c07d6582c8954
Signed-off-by: Martin Ciglan <martin.ciglan@pantheon.tech>
(cherry picked from commit 357db495133d3d275582e9ea4b5063c15e9a5f5a)

binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/BitsCodec.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/CompositeValueCodec.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/EncapsulatedValueCodec.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/EnumerationCodec.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/ReflectionBasedCodec.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/ValueTypeCodec.java [new file with mode: 0644]

diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/BitsCodec.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/BitsCodec.java
new file mode 100644 (file)
index 0000000..e1dac75
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.mdsal.binding.javav2.dom.codec.impl.value;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.value.ValueTypeCodec.SchemaUnawareCodec;
+import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier;
+import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifierNormalizer;
+import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
+
+@Beta
+final class BitsCodec extends ReflectionBasedCodec implements SchemaUnawareCodec {
+
+    private static final MethodType CONSTRUCTOR_INVOKE_TYPE = MethodType.methodType(Object.class, Boolean[].class);
+    // Ordered by position
+    private final Map<String, Method> getters;
+    // Ordered by lexical name
+    private final Set<String> ctorArgs;
+    private final MethodHandle ctor;
+
+    private BitsCodec(final Class<?> typeClass, final MethodHandle ctor, final Set<String> ctorArgs,
+            final Map<String, Method> getters) {
+
+        super(typeClass);
+        this.ctor = Preconditions.checkNotNull(ctor);
+        this.ctorArgs = ImmutableSet.copyOf(ctorArgs);
+        this.getters = ImmutableMap.copyOf(getters);
+    }
+
+    static Callable<BitsCodec> loader(final Class<?> returnType, final BitsTypeDefinition rootType) {
+        return () -> {
+            final Map<String, Method> getters = new LinkedHashMap<>();
+            final Set<String> ctorArgs = new TreeSet<>();
+
+            for (Bit bit : rootType.getBits()) {
+                final Method valueGetter = returnType.getMethod("is" + JavaIdentifierNormalizer
+                        .normalizeSpecificIdentifier(bit.getName(), JavaIdentifier.CLASS));
+                ctorArgs.add(bit.getName());
+                getters.put(bit.getName(), valueGetter);
+            }
+            Constructor<?> constructor = null;
+            for (Constructor<?> cst : returnType.getConstructors()) {
+                if (!cst.getParameterTypes()[0].equals(returnType)) {
+                    constructor = cst;
+                }
+            }
+
+            final MethodHandle ctor = MethodHandles.publicLookup().unreflectConstructor(constructor)
+                    .asSpreader(Boolean[].class, ctorArgs.size()).asType(CONSTRUCTOR_INVOKE_TYPE);
+            return new BitsCodec(returnType, ctor, ctorArgs, getters);
+        };
+    }
+
+    @Override
+    public Object deserialize(Object input) {
+        Preconditions.checkArgument(input instanceof Set);
+        @SuppressWarnings("unchecked")
+        final Set<String> casted = (Set<String>) input;
+
+        /*
+         * We can do this walk based on field set sorted by name,
+         * since constructor arguments in Java Binding are sorted by name.
+         *
+         * This means we will construct correct array for construction
+         * of bits object.
+         */
+        final Boolean args[] = new Boolean[ctorArgs.size()];
+        int currentArg = 0;
+        for (String value : ctorArgs) {
+            args[currentArg++] = casted.contains(value);
+        }
+
+        try {
+            return ctor.invokeExact(args);
+        } catch (Throwable e) {
+            throw new IllegalStateException("Failed to instantiate object for " + input, e);
+        }
+    }
+
+    @Override
+    public Object serialize(Object input) {
+        final Collection<String> result = new ArrayList<>(getters.size());
+        for (Entry<String, Method> valueGet : getters.entrySet()) {
+            final Boolean value;
+            try {
+                value = (Boolean) valueGet.getValue().invoke(input);
+            } catch (IllegalAccessException | InvocationTargetException e) {
+                throw new IllegalArgumentException("Failed to get bit " + valueGet.getKey(), e);
+            }
+
+            if (value) {
+                result.add(valueGet.getKey());
+            }
+        }
+        return result.size() == getters.size() ? getters.keySet() : ImmutableSet.copyOf(result);
+    }
+}
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/CompositeValueCodec.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/CompositeValueCodec.java
new file mode 100644 (file)
index 0000000..ee8d758
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.mdsal.binding.javav2.dom.codec.impl.value;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.yangtools.concepts.Codec;
+
+@Beta
+final class CompositeValueCodec extends ValueTypeCodec {
+
+    private final SchemaUnawareCodec bindingToSimpleType;
+
+    @SuppressWarnings("rawtypes")
+    private final Codec bindingToDom;
+
+    CompositeValueCodec(final SchemaUnawareCodec extractor, @SuppressWarnings("rawtypes") final Codec delegate) {
+        this.bindingToSimpleType = extractor;
+        this.bindingToDom = delegate;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Object deserialize(Object input) {
+        return bindingToSimpleType.deserialize(bindingToDom.deserialize(input));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Object serialize(Object input) {
+        return bindingToDom.serialize(bindingToSimpleType.serialize(input));
+    }
+}
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/EncapsulatedValueCodec.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/EncapsulatedValueCodec.java
new file mode 100644 (file)
index 0000000..8727ead
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.mdsal.binding.javav2.dom.codec.impl.value;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.value.ValueTypeCodec.SchemaUnawareCodec;
+
+/**
+ * Derived YANG types are just immutable value holders for simple value
+ * types, which are same as in NormalizedNode model.
+ */
+@Beta
+final class EncapsulatedValueCodec extends ReflectionBasedCodec implements SchemaUnawareCodec {
+
+    private static final Lookup LOOKUP = MethodHandles.publicLookup();
+    private static final MethodType OBJ_METHOD = MethodType.methodType(Object.class, Object.class);
+    private final MethodHandle constructor;
+    private final MethodHandle getter;
+    private final Class<?> valueType;
+
+    private EncapsulatedValueCodec(final Class<?> typeClz, final MethodHandle constructor, final MethodHandle getter,
+            final Class<?> valueType) {
+
+        super(typeClz);
+        this.constructor = Preconditions.checkNotNull(constructor);
+        this.getter = Preconditions.checkNotNull(getter);
+        this.valueType = Preconditions.checkNotNull(valueType);
+    }
+
+    static Callable<EncapsulatedValueCodec> loader(final Class<?> typeClz) {
+        return () -> {
+            final Method m = typeClz.getMethod("getValue");
+            final MethodHandle getter = LOOKUP.unreflect(m).asType(OBJ_METHOD);
+            final Class<?> valueType = m.getReturnType();
+
+            final MethodHandle constructor = LOOKUP.findConstructor(typeClz,
+                    MethodType.methodType(void.class, valueType)).asType(OBJ_METHOD);
+            return new EncapsulatedValueCodec(typeClz, constructor, getter, valueType);
+        };
+    }
+
+    /**
+     * Quick check if a value object has a chance to deserialize using {@link #deserialize(Object)}.
+     *
+     * @param value Value to be checked
+     * @return True if the value can be encapsulated
+     */
+    boolean canAcceptObject(final Object value) {
+        return valueType.isInstance(value);
+    }
+
+    @Override
+    public Object deserialize(Object input) {
+        try {
+            return constructor.invokeExact(input);
+        } catch (Throwable e) {
+            throw Throwables.propagate(e);
+        }
+    }
+
+    @Override
+    public Object serialize(Object input) {
+        try {
+            return getter.invokeExact(input);
+        } catch (Throwable e) {
+            throw Throwables.propagate(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/EnumerationCodec.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/EnumerationCodec.java
new file mode 100644 (file)
index 0000000..3c88f36
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.mdsal.binding.javav2.dom.codec.impl.value;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableBiMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.value.ValueTypeCodec.SchemaUnawareCodec;
+import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier;
+import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifierNormalizer;
+import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
+
+@Beta
+final class EnumerationCodec extends ReflectionBasedCodec implements SchemaUnawareCodec {
+
+    private final ImmutableBiMap<String, Enum<?>> yangValueToBinding;
+
+    private EnumerationCodec(final Class<? extends Enum<?>> enumeration, final Map<String, Enum<?>> schema) {
+        super(enumeration);
+        yangValueToBinding = ImmutableBiMap.copyOf(schema);
+    }
+
+    static Callable<EnumerationCodec> loader(final Class<?> returnType, final EnumTypeDefinition enumSchema) {
+        Preconditions.checkArgument(Enum.class.isAssignableFrom(returnType));
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        final Class<? extends Enum<?>> enumType = (Class) returnType;
+        return () -> {
+            Map<String, Enum<?>> nameToValue = new HashMap<>();
+            for (Enum<?> enumValue : enumType.getEnumConstants()) {
+                nameToValue.put(enumValue.toString(), enumValue);
+            }
+            Map<String, Enum<?>> yangNameToBinding = new HashMap<>();
+            for (EnumPair yangValue : enumSchema.getValues()) {
+                final String bindingName = JavaIdentifierNormalizer.normalizeSpecificIdentifier(yangValue.getName(),
+                        JavaIdentifier.CLASS);
+                final Enum<?> bindingVal = nameToValue.get(bindingName);
+                yangNameToBinding.put(yangValue.getName(), bindingVal);
+            }
+            return new EnumerationCodec(enumType, yangNameToBinding);
+        };
+    }
+
+    @Override
+    public Object deserialize(Object input) {
+        Enum<?> value = yangValueToBinding.get(input);
+        Preconditions.checkArgument(value != null, "Invalid enumeration value %s. Valid values are %s", input,
+                yangValueToBinding.keySet());
+        return value;
+    }
+
+    @Override
+    public Object serialize(Object input) {
+        Preconditions.checkArgument(getTypeClass().isInstance(input), "Input must be instance of %s", getTypeClass());
+        return yangValueToBinding.inverse().get(input);
+    }
+}
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/ReflectionBasedCodec.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/ReflectionBasedCodec.java
new file mode 100644 (file)
index 0000000..0c0c471
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.mdsal.binding.javav2.dom.codec.impl.value;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+
+@Beta
+abstract class ReflectionBasedCodec extends ValueTypeCodec {
+
+    private final Class<?> typeClass;
+
+    ReflectionBasedCodec(final Class<?> typeClass) {
+        this.typeClass = Preconditions.checkNotNull(typeClass);
+    }
+
+    protected final Class<?> getTypeClass() {
+        return typeClass;
+    }
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/ValueTypeCodec.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/value/ValueTypeCodec.java
new file mode 100644 (file)
index 0000000..77dfdc4
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.mdsal.binding.javav2.dom.codec.impl.value;
+
+import com.google.common.annotations.Beta;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import org.opendaylight.mdsal.binding.javav2.spec.util.BindingReflections;
+import org.opendaylight.yangtools.concepts.Codec;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
+
+/**
+ * Value codec, which serializes / de-serializes values from DOM simple values.
+ */
+@Beta
+abstract class ValueTypeCodec implements Codec<Object, Object> {
+
+    private static final Cache<Class<?>, SchemaUnawareCodec> staticCodecs = CacheBuilder.newBuilder().weakKeys()
+            .build();
+
+    /**
+     * Marker interface for codecs, which functionality will not be
+     * affected by schema change (introduction of new YANG modules)
+     * they may have one static instance generated when
+     * first time needed.
+     */
+    interface SchemaUnawareCodec extends Codec<Object,Object> {
+    }
+
+    /**
+     *
+     * No-op Codec, Java YANG Binding uses same types as NormalizedNode model
+     * for base YANG types, representing numbers, binary and strings.
+     */
+    public static final SchemaUnawareCodec NOOP_CODEC = new SchemaUnawareCodec() {
+
+        @Override
+        public Object serialize(final Object input) {
+            return input;
+        }
+
+        @Override
+        public Object deserialize(final Object input) {
+            return input;
+        }
+    };
+
+    public static final SchemaUnawareCodec EMPTY_CODEC = new SchemaUnawareCodec() {
+
+        @Override
+        public Object serialize(final Object arg0) {
+            // Empty type has null value in NormalizedNode and Composite Node
+            // representation
+            return null;
+        }
+
+        @Override
+        public Object deserialize(final Object arg0) {
+            /* Empty type has boolean.TRUE representation in Binding-aware world
+            *  otherwise it is null / false.
+            *  So when codec is triggered, empty leaf is present, that means we
+            *  are safe to return true.
+            */
+            return Boolean.TRUE;
+        }
+    };
+
+    private static final Callable<? extends SchemaUnawareCodec> EMPTY_LOADER = new Callable<SchemaUnawareCodec>() {
+
+        @Override
+        public SchemaUnawareCodec call() {
+            return EMPTY_CODEC;
+        }
+    };
+
+    public static SchemaUnawareCodec getCodecFor(final Class<?> typeClz, final TypeDefinition<?> def) {
+        if (BindingReflections.isBindingClass(typeClz)) {
+            return getCachedSchemaUnawareCodec(typeClz, getCodecLoader(typeClz, def));
+        }
+        return def instanceof EmptyTypeDefinition ? EMPTY_CODEC : NOOP_CODEC;
+    }
+
+    private static SchemaUnawareCodec getCachedSchemaUnawareCodec(final Class<?> typeClz, final Callable<? extends SchemaUnawareCodec> loader) {
+        try {
+            return staticCodecs.get(typeClz, loader);
+        } catch (ExecutionException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private static Callable<? extends SchemaUnawareCodec> getCodecLoader(final Class<?> typeClz, final TypeDefinition<?> def) {
+
+        TypeDefinition<?> rootType = def;
+        while (rootType.getBaseType() != null) {
+            rootType = rootType.getBaseType();
+        }
+        if (rootType instanceof EnumTypeDefinition) {
+            return EnumerationCodec.loader(typeClz, (EnumTypeDefinition) rootType);
+        } else if (rootType instanceof BitsTypeDefinition) {
+            return BitsCodec.loader(typeClz, (BitsTypeDefinition) rootType);
+        } else if (rootType instanceof EmptyTypeDefinition) {
+            return EMPTY_LOADER;
+        }
+        return EncapsulatedValueCodec.loader(typeClz);
+    }
+
+    @SuppressWarnings("rawtypes")
+    static ValueTypeCodec encapsulatedValueCodecFor(final Class<?> typeClz, final Codec delegate) {
+        SchemaUnawareCodec extractor = getCachedSchemaUnawareCodec(typeClz, EncapsulatedValueCodec.loader(typeClz));
+        return new CompositeValueCodec(extractor, delegate);
+    }
+}
\ No newline at end of file