Improve SchemaUnawareCodec
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / EnumerationCodec.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.mdsal.binding.dom.codec.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.cache.Cache;
15 import com.google.common.cache.CacheBuilder;
16 import com.google.common.collect.ImmutableBiMap;
17 import com.google.common.collect.Maps;
18 import java.util.Arrays;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.concurrent.ExecutionException;
22 import java.util.stream.Collectors;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.opendaylight.yangtools.yang.binding.EnumTypeObject;
25 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
26 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 final class EnumerationCodec extends SchemaUnawareCodec {
31     private static final Logger LOG = LoggerFactory.getLogger(EnumerationCodec.class);
32     /*
33      * Use identity comparison for keys and allow classes to be GCd themselves.
34      *
35      * Since codecs can (and typically do) hold a direct or indirect strong reference to the class, they need to be also
36      * accessed via reference. Using a weak reference could be problematic, because the codec would quite often be only
37      * weakly reachable. We therefore use a soft reference, whose implementation guidance is suitable to our use case:
38      *
39      *     "Virtual machine implementations are, however, encouraged to bias against clearing recently-created or
40      *      recently-used soft references."
41      */
42     private static final Cache<Class<? extends EnumTypeObject>, @NonNull EnumerationCodec> CACHE =
43         CacheBuilder.newBuilder().weakKeys().softValues().build();
44
45     private final ImmutableBiMap<String, Enum<?>> nameToEnum;
46     private final Class<? extends Enum<?>> enumClass;
47
48     private EnumerationCodec(final Class<? extends Enum<?>> enumClass, final Map<String, Enum<?>> nameToEnum) {
49         this.enumClass = requireNonNull(enumClass);
50         this.nameToEnum = ImmutableBiMap.copyOf(nameToEnum);
51     }
52
53     static @NonNull EnumerationCodec of(final Class<?> returnType, final EnumTypeDefinition def)
54             throws ExecutionException {
55         return CACHE.get(returnType.asSubclass(EnumTypeObject.class), () -> {
56             final Class<? extends Enum<?>> enumType = castType(returnType);
57
58             final Map<String, Enum<?>> mapping = Maps.uniqueIndex(Arrays.asList(enumType.getEnumConstants()),
59                 value -> {
60                     checkArgument(value instanceof EnumTypeObject,
61                         "Enumeration constant %s.%s is not implementing EnumTypeObject", enumType.getName(), value);
62                     return ((EnumTypeObject) value).getName();
63                 });
64
65             // Check if mapping is a bijection
66             final Set<String> assignedNames =  def.getValues().stream().map(EnumPair::getName)
67                     .collect(Collectors.toSet());
68             for (String name : assignedNames) {
69                 if (!mapping.containsKey(name)) {
70                     LOG.warn("Enumeration {} does not contain assigned name '{}' from {}", enumType, name, def);
71                 }
72             }
73             for (String name : mapping.keySet()) {
74                 if (!assignedNames.contains(name)) {
75                     LOG.warn("Enumeration {} contains assigned name '{}' not covered by {}", enumType, name, def);
76                 }
77             }
78
79             return new EnumerationCodec(enumType, mapping);
80         });
81     }
82
83     @SuppressWarnings("unchecked")
84     private static Class<? extends Enum<?>> castType(final Class<?> returnType) {
85         checkArgument(Enum.class.isAssignableFrom(returnType));
86         return (Class<? extends Enum<?>>) returnType.asSubclass(Enum.class);
87     }
88
89     @Override
90     protected Enum<?> deserializeImpl(final Object input) {
91         checkArgument(input instanceof String, "Input %s is not a String", input);
92         final Enum<?> value = nameToEnum.get(input);
93         checkArgument(value != null, "Invalid enumeration value %s. Valid values are %s", input, nameToEnum.keySet());
94         return value;
95     }
96
97     @Override
98     protected String serializeImpl(final Object input) {
99         checkArgument(enumClass.isInstance(input), "Input %s is not a instance of %s", input, enumClass);
100         return verifyNotNull(nameToEnum.inverse().get(input));
101     }
102 }