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
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.binding.data.codec.impl;
9
10 import com.google.common.collect.ImmutableSet;
11 import com.google.common.io.BaseEncoding;
12 import com.google.common.util.concurrent.ExecutionError;
13 import com.google.common.util.concurrent.UncheckedExecutionException;
14 import java.lang.invoke.MethodHandle;
15 import java.lang.invoke.MethodHandles;
16 import java.lang.invoke.MethodType;
17 import java.lang.reflect.Method;
18 import java.util.HashSet;
19 import java.util.Set;
20 import java.util.concurrent.Callable;
21 import javax.annotation.Nullable;
22 import org.opendaylight.yangtools.concepts.Codec;
23 import org.opendaylight.yangtools.yang.binding.BindingMapping;
24 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
25 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 final class UnionTypeCodec extends ReflectionBasedCodec {
30     private static final MethodType CHARARRAY_LOOKUP_TYPE = MethodType.methodType(void.class, char[].class);
31     private static final MethodType CHARARRAY_INVOKE_TYPE = MethodType.methodType(Object.class, char[].class);
32     private static final MethodType CLASS_LOOKUP_TYPE = MethodType.methodType(void.class, Class.class);
33     private static final MethodType CLASS_INVOKE_TYPE = MethodType.methodType(Object.class, Object.class);
34     private static final Logger LOG = LoggerFactory.getLogger(UnionTypeCodec.class);
35
36     private final Codec<Object, Object> idRefCodec;
37     private final MethodHandle idrefConstructor;
38
39     private final ImmutableSet<UnionValueOptionContext> typeCodecs;
40     private final MethodHandle charConstructor;
41
42     private UnionTypeCodec(final Class<?> unionCls,final Set<UnionValueOptionContext> codecs,
43                            @Nullable final Codec<Object, Object> identityrefCodec) {
44         super(unionCls);
45         this.idRefCodec = identityrefCodec;
46         if (idRefCodec != null) {
47             try {
48                 idrefConstructor = MethodHandles.publicLookup().findConstructor(unionCls, CLASS_LOOKUP_TYPE)
49                         .asType(CLASS_INVOKE_TYPE);
50             } catch (IllegalAccessException | NoSuchMethodException e) {
51                 throw new IllegalStateException("Failed to get identityref constructor", e);
52             }
53         } else {
54             idrefConstructor = null;
55         }
56
57         try {
58             charConstructor = MethodHandles.publicLookup().findConstructor(unionCls, CHARARRAY_LOOKUP_TYPE)
59                     .asType(CHARARRAY_INVOKE_TYPE);
60         } catch (IllegalAccessException | NoSuchMethodException e) {
61             throw new IllegalStateException("Failed to instantiate handle for constructor", e);
62         }
63
64         typeCodecs = ImmutableSet.copyOf(codecs);
65     }
66
67     static Callable<UnionTypeCodec> loader(final Class<?> unionCls, final UnionTypeDefinition unionType,
68                                            final BindingCodecContext bindingCodecContext) {
69         return new Callable<UnionTypeCodec>() {
70             @Override
71             public UnionTypeCodec call() throws NoSuchMethodException, SecurityException {
72                 Codec<Object, Object> identityrefCodec = null;
73                 Set<UnionValueOptionContext> values = new HashSet<>();
74                 for (TypeDefinition<?> subtype : unionType.getTypes()) {
75                     String methodName = "get" + BindingMapping.getClassName(subtype.getQName());
76                     Method valueGetter = unionCls.getMethod(methodName);
77                     Class<?> valueType = valueGetter.getReturnType();
78                     Codec<Object, Object> valueCodec = bindingCodecContext.getCodec(valueType, subtype);
79                     if (Class.class.equals(valueType)) {
80                         identityrefCodec = valueCodec;
81                     }
82                     values.add(new UnionValueOptionContext(valueType,valueGetter, valueCodec));
83                 }
84                 return new UnionTypeCodec(unionCls, values, identityrefCodec);
85             }
86         };
87     }
88
89     private Object deserializeString(final Object input) {
90         final String str = input instanceof byte[] ? BaseEncoding.base64().encode((byte[]) input) : input.toString();
91         try {
92             return charConstructor.invokeExact(str.toCharArray());
93         } catch (Throwable e) {
94             throw new IllegalStateException("Could not construct instance", e);
95         }
96     }
97
98     @Override
99     public Object deserialize(final Object input) {
100         if (idRefCodec != null) {
101             final Object identityref;
102             try {
103                 identityref = idRefCodec.deserialize(input);
104             } catch (UncheckedExecutionException | ExecutionError | ClassCastException e) {
105                 LOG.debug("Deserialization of {} as identityref failed", e);
106                 return deserializeString(input);
107             }
108
109             try {
110                 return idrefConstructor.invokeExact(identityref);
111             } catch (Throwable e) {
112                 LOG.debug("Failed to instantiate based on identityref {}", identityref, e);
113             }
114         }
115
116         return deserializeString(input);
117     }
118
119     @Override
120     public Object serialize(final Object input) {
121         if (input != null) {
122             for (UnionValueOptionContext valCtx : typeCodecs) {
123                 Object domValue = valCtx.serialize(input);
124                 if (domValue != null) {
125                     return domValue;
126                 }
127             }
128         }
129         return null;
130     }
131 }