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