Bug 6112 - UnionTypeCodec fails to non-identityref value
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / yangtools / binding / data / codec / impl / UnionTypeCodec.java
index 00702f07296d5e92d886d9020a746f9d67e3bd3a..d05503f5818031b080c05c72fc4aa57d203f90c6 100644 (file)
 package org.opendaylight.yangtools.binding.data.codec.impl;
 
 import com.google.common.collect.ImmutableSet;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
+import com.google.common.io.BaseEncoding;
+import com.google.common.util.concurrent.ExecutionError;
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
 import java.lang.reflect.Method;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import javax.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.yang.binding.BindingMapping;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
-import org.opendaylight.yangtools.yang.model.util.UnionType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 final class UnionTypeCodec extends ReflectionBasedCodec {
+    private static final MethodType CHARARRAY_LOOKUP_TYPE = MethodType.methodType(void.class, char[].class);
+    private static final MethodType CHARARRAY_INVOKE_TYPE = MethodType.methodType(Object.class, char[].class);
+    private static final MethodType CLASS_LOOKUP_TYPE = MethodType.methodType(void.class, Class.class);
+    private static final MethodType CLASS_INVOKE_TYPE = MethodType.methodType(Object.class, Object.class);
+    private static final Logger LOG = LoggerFactory.getLogger(UnionTypeCodec.class);
+
+    private final Codec<Object, Object> idRefCodec;
+    private final MethodHandle idrefConstructor;
 
     private final ImmutableSet<UnionValueOptionContext> typeCodecs;
-    private final Constructor<?> charConstructor;
+    private final MethodHandle charConstructor;
 
-    private UnionTypeCodec(final Class<?> unionCls,final Set<UnionValueOptionContext> codecs) {
+    private UnionTypeCodec(final Class<?> unionCls,final Set<UnionValueOptionContext> codecs,
+                           @Nullable final Codec<Object, Object> identityrefCodec) {
         super(unionCls);
+        this.idRefCodec = identityrefCodec;
+        if (idRefCodec != null) {
+            try {
+                idrefConstructor = MethodHandles.publicLookup().findConstructor(unionCls, CLASS_LOOKUP_TYPE)
+                        .asType(CLASS_INVOKE_TYPE);
+            } catch (IllegalAccessException | NoSuchMethodException e) {
+                throw new IllegalStateException("Failed to get identityref constructor", e);
+            }
+        } else {
+            idrefConstructor = null;
+        }
+
         try {
-            charConstructor = unionCls.getConstructor(char[].class);
-            typeCodecs = ImmutableSet.copyOf(codecs);
-        } catch (NoSuchMethodException | SecurityException e) {
-           throw new IllegalStateException("Required constructor is not available.",e);
+            charConstructor = MethodHandles.publicLookup().findConstructor(unionCls, CHARARRAY_LOOKUP_TYPE)
+                    .asType(CHARARRAY_INVOKE_TYPE);
+        } catch (IllegalAccessException | NoSuchMethodException e) {
+            throw new IllegalStateException("Failed to instantiate handle for constructor", e);
         }
+
+        typeCodecs = ImmutableSet.copyOf(codecs);
     }
 
-    static Callable<UnionTypeCodec> loader(final Class<?> unionCls, final UnionTypeDefinition unionType) {
+    static Callable<UnionTypeCodec> loader(final Class<?> unionCls, final UnionTypeDefinition unionType,
+                                           final BindingCodecContext bindingCodecContext) {
         return new Callable<UnionTypeCodec>() {
             @Override
             public UnionTypeCodec call() throws NoSuchMethodException, SecurityException {
+                Codec<Object, Object> identityrefCodec = null;
                 Set<UnionValueOptionContext> values = new HashSet<>();
-                for(TypeDefinition<?> subtype : unionType.getTypes()) {
+                for (TypeDefinition<?> subtype : unionType.getTypes()) {
                     String methodName = "get" + BindingMapping.getClassName(subtype.getQName());
                     Method valueGetter = unionCls.getMethod(methodName);
                     Class<?> valueType = valueGetter.getReturnType();
-                    Codec<Object, Object> valueCodec = UnionTypeCodec.getCodecForType(valueType, subtype);
+                    Codec<Object, Object> valueCodec = bindingCodecContext.getCodec(valueType, subtype);
+                    if (Class.class.equals(valueType)) {
+                        identityrefCodec = valueCodec;
+                    }
                     values.add(new UnionValueOptionContext(valueType,valueGetter, valueCodec));
                 }
-                return new UnionTypeCodec(unionCls, values);
+                return new UnionTypeCodec(unionCls, values, identityrefCodec);
             }
         };
     }
 
-    private static Codec<Object, Object> getCodecForType(final Class<?> valueType, final TypeDefinition<?> subtype) {
-        if (subtype.getBaseType() instanceof UnionType) {
-            try {
-                return UnionTypeCodec.loader(valueType, (UnionType) subtype.getBaseType()).call();
-            } catch (final Exception e) {
-                throw new IllegalStateException("Could not construct Union Type Codec");
-            }
-        } else {
-            return ValueTypeCodec.getCodecFor(valueType, subtype);
+    private Object deserializeString(final Object input) {
+        final String str = input instanceof byte[] ? BaseEncoding.base64().encode((byte[]) input) : input.toString();
+        try {
+            return charConstructor.invokeExact(str.toCharArray());
+        } catch (Throwable e) {
+            throw new IllegalStateException("Could not construct instance", e);
         }
     }
 
     @Override
     public Object deserialize(final Object input) {
-        try {
-            return charConstructor.newInstance((input.toString().toCharArray()));
-        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
-            throw new IllegalStateException("Could not construct instance",e);
+        if (idRefCodec != null) {
+            final Object identityref;
+            try {
+                identityref = idRefCodec.deserialize(input);
+            } catch (UncheckedExecutionException | ExecutionError | ClassCastException e) {
+                LOG.debug("Deserialization of {} as identityref failed", e);
+                return deserializeString(input);
+            }
+
+            try {
+                return idrefConstructor.invokeExact(identityref);
+            } catch (Throwable e) {
+                LOG.debug("Failed to instantiate based on identityref {}", identityref, e);
+            }
         }
+
+        return deserializeString(input);
     }
 
     @Override
@@ -85,5 +128,4 @@ final class UnionTypeCodec extends ReflectionBasedCodec {
         }
         return null;
     }
-
 }