Split STATIC_CODECS into per-type caches
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / EnumerationCodec.java
index ebe9c7d5d5c399d2f294a3c6da231138fb61da54..076ff1935f135b9a014c5c7ff1347ec14cd17393 100644 (file)
@@ -8,58 +8,94 @@
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
 import static java.util.Objects.requireNonNull;
 
-import com.google.common.collect.BiMap;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
 import com.google.common.collect.ImmutableBiMap;
-import com.google.common.collect.ImmutableBiMap.Builder;
-import java.util.concurrent.Callable;
+import com.google.common.collect.Maps;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.binding.dom.codec.impl.ValueTypeCodec.SchemaUnawareCodec;
-import org.opendaylight.yangtools.yang.binding.BindingMapping;
+import org.opendaylight.yangtools.yang.binding.Enumeration;
 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 final class EnumerationCodec extends ReflectionBasedCodec implements SchemaUnawareCodec {
-    private final ImmutableBiMap<String, Enum<?>> yangValueToBinding;
+    private static final Logger LOG = LoggerFactory.getLogger(EnumerationCodec.class);
+    /*
+     * Use identity comparison for keys and allow classes to be GCd themselves.
+     *
+     * Since codecs can (and typically do) hold a direct or indirect strong reference to the class, they need to be also
+     * accessed via reference. Using a weak reference could be problematic, because the codec would quite often be only
+     * weakly reachable. We therefore use a soft reference, whose implementation guidance is suitable to our use case:
+     *
+     *     "Virtual machine implementations are, however, encouraged to bias against clearing recently-created or
+     *      recently-used soft references."
+     */
+    private static final Cache<Class<?>, @NonNull EnumerationCodec> CACHE = CacheBuilder.newBuilder().weakKeys()
+        .softValues().build();
 
-    EnumerationCodec(final Class<? extends Enum<?>> enumeration, final ImmutableBiMap<String, Enum<?>> schema) {
+    private final ImmutableBiMap<String, Enum<?>> nameToEnum;
+
+    private EnumerationCodec(final Class<? extends Enum<?>> enumeration, final Map<String, Enum<?>> nameToEnum) {
         super(enumeration);
-        yangValueToBinding = requireNonNull(schema);
+        this.nameToEnum = ImmutableBiMap.copyOf(nameToEnum);
     }
 
-    static Callable<EnumerationCodec> loader(final Class<?> returnType, final EnumTypeDefinition enumSchema) {
-        checkArgument(Enum.class.isAssignableFrom(returnType));
-        @SuppressWarnings({ "rawtypes", "unchecked" })
-        final Class<? extends Enum<?>> enumType = (Class) returnType;
-        return () -> {
-            final BiMap<String, String> identifierToYang = BindingMapping.mapEnumAssignedNames(
-                enumSchema.getValues().stream().map(EnumPair::getName).collect(Collectors.toList())).inverse();
+    static @NonNull EnumerationCodec of(final Class<?> returnType, final EnumTypeDefinition def)
+            throws ExecutionException {
+        return CACHE.get(returnType, () -> {
+            final Class<? extends Enum<?>> enumType = castType(returnType);
 
-            final Builder<String, Enum<?>> builder = ImmutableBiMap.builder();
-            for (Enum<?> enumValue : enumType.getEnumConstants()) {
-                final String yangName = identifierToYang.get(enumValue.name());
-                checkState(yangName != null, "Failed to find enumeration constant %s in mapping %s", enumValue,
-                        identifierToYang);
-                builder.put(yangName, enumValue);
+            final Map<String, Enum<?>> mapping = Maps.uniqueIndex(Arrays.asList(enumType.getEnumConstants()),
+                value -> {
+                    checkArgument(value instanceof Enumeration,
+                        "Enumeration constant %s.%s is not implementing Enumeration", enumType.getName(), value);
+                    return ((Enumeration) value).getName();
+                });
+
+            // Check if mapping is a bijection
+            final Set<String> assignedNames =  def.getValues().stream().map(EnumPair::getName)
+                    .collect(Collectors.toSet());
+            for (String name : assignedNames) {
+                if (!mapping.containsKey(name)) {
+                    LOG.warn("Enumeration {} does not contain assigned name '{}' from {}", enumType, name, def);
+                }
+            }
+            for (String name : mapping.keySet()) {
+                if (!assignedNames.contains(name)) {
+                    LOG.warn("Enumeration {} contains assigned name '{}' not covered by {}", enumType, name, def);
+                }
             }
 
-            return new EnumerationCodec(enumType, builder.build());
-        };
+            return new EnumerationCodec(enumType, mapping);
+        });
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Class<? extends Enum<?>> castType(final Class<?> returnType) {
+        checkArgument(Enum.class.isAssignableFrom(returnType));
+        return (Class<? extends Enum<?>>) returnType;
     }
 
     @Override
-    public Object deserialize(final Object input) {
-        Enum<?> value = yangValueToBinding.get(input);
-        checkArgument(value != null, "Invalid enumeration value %s. Valid values are %s", input,
-                yangValueToBinding.keySet());
+    public Enum<?> deserialize(final Object input) {
+        checkArgument(input instanceof String, "Input %s is not a String", input);
+        final Enum<?> value = nameToEnum.get(input);
+        checkArgument(value != null, "Invalid enumeration value %s. Valid values are %s", input, nameToEnum.keySet());
         return value;
     }
 
     @Override
-    public Object serialize(final Object input) {
-        checkArgument(getTypeClass().isInstance(input), "Input must be instance of %s", getTypeClass());
-        return yangValueToBinding.inverse().get(input);
+    public String serialize(final Object input) {
+        checkArgument(getTypeClass().isInstance(input), "Input %s is not a instance of %s", input, getTypeClass());
+        return requireNonNull(nameToEnum.inverse().get(input));
     }
 }
\ No newline at end of file