Loosen BindingDataContainerCodecTreeNode.getBindingClass()
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / IdentityCodec.java
index acd723108be7adee1fbeaaad25508cd9720326c9..3fa1a9c03562da03cc048f9906ba2a39648b3f86 100644 (file)
@@ -7,17 +7,69 @@
  */
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
-import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 
+import com.google.common.base.Throwables;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingIdentityCodec;
-import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
-import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
-import org.opendaylight.yangtools.concepts.Codec;
+import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext;
+import org.opendaylight.mdsal.binding.runtime.api.IdentityRuntimeType;
 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
+import org.opendaylight.yangtools.yang.binding.contract.Naming;
 import org.opendaylight.yangtools.yang.common.QName;
 
-final class IdentityCodec implements Codec<QName, Class<?>>, BindingIdentityCodec {
+final class IdentityCodec extends AbstractValueCodec<QName, BaseIdentity> implements BindingIdentityCodec {
+    private final LoadingCache<@NonNull QName, @NonNull BaseIdentity> values = CacheBuilder.newBuilder()
+        .build(new CacheLoader<>() {
+            @Override
+            public BaseIdentity load(final QName key) {
+                final var clazz = context.getIdentityClass(key);
+                final Field field;
+                try {
+                    field = clazz.getField(Naming.VALUE_STATIC_FIELD_NAME);
+                } catch (NoSuchFieldException e) {
+                    throw new LinkageError(clazz + " does not define required field " + Naming.VALUE_STATIC_FIELD_NAME,
+                        e);
+                }
+                if (!Modifier.isStatic(field.getModifiers())) {
+                    throw new LinkageError(field + " is not static");
+                }
+
+                final Object value;
+                try {
+                    value = clazz.cast(field.get(null));
+                } catch (IllegalAccessException e) {
+                    throw new LinkageError(field + " is not accesssible", e);
+                }
+                if (value == null) {
+                    throw new LinkageError(field + " is null");
+                }
+                try {
+                    return clazz.cast(value);
+                } catch (ClassCastException e) {
+                    throw new LinkageError(field + " value " + value + " has illegal type", e);
+                }
+            }
+        });
+    private final LoadingCache<@NonNull Class<? extends BaseIdentity>, @NonNull QName> qnames =
+        // Note: weak keys because it is the user who is supplying implemented contract
+        CacheBuilder.newBuilder().weakKeys().build(new CacheLoader<>() {
+            @Override
+            public QName load(final Class<? extends BaseIdentity> key) {
+                final var schema = context.getTypeWithSchema(key);
+                if (schema instanceof IdentityRuntimeType identitySchema) {
+                    return identitySchema.statement().argument();
+                }
+                throw new IllegalStateException("Unexpected schema " + schema + " for " + key);
+            }
+        });
+
     private final BindingRuntimeContext context;
 
     IdentityCodec(final BindingRuntimeContext context) {
@@ -25,26 +77,33 @@ final class IdentityCodec implements Codec<QName, Class<?>>, BindingIdentityCode
     }
 
     @Override
-    public Class<?> deserialize(final QName input) {
-        checkArgument(input != null, "Input must not be null.");
-        return context.getIdentityClass(input);
+    protected BaseIdentity deserializeImpl(final QName input) {
+        return toBinding(input);
     }
 
     @Override
-    public QName serialize(final Class<?> input) {
-        checkArgument(BaseIdentity.class.isAssignableFrom(input), "%s is not an identity", input);
-        return BindingReflections.findQName(input);
+    protected QName serializeImpl(final BaseIdentity input) {
+        return fromBinding(input);
     }
 
     @Override
-    public Class<? extends BaseIdentity> toBinding(final QName qname) {
-        final Class<?> identity = context.getIdentityClass(requireNonNull(qname));
-        checkArgument(BaseIdentity.class.isAssignableFrom(identity), "%s resolves to non-identity %s", qname, identity);
-        return identity.asSubclass(BaseIdentity.class);
+    @SuppressWarnings("unchecked")
+    public <T extends BaseIdentity> T toBinding(final QName qname) {
+        try {
+            return (T) values.get(requireNonNull(qname));
+        } catch (ExecutionException e) {
+            Throwables.throwIfUnchecked(e.getCause());
+            throw new IllegalStateException("Unexpected error translating " + qname, e);
+        }
     }
 
     @Override
-    public QName fromBinding(final Class<? extends BaseIdentity> bindingClass) {
-        return BindingReflections.getQName(bindingClass);
+    public QName fromBinding(final BaseIdentity bindingValue) {
+        try {
+            return qnames.get(bindingValue.implementedInterface());
+        } catch (ExecutionException e) {
+            Throwables.throwIfUnchecked(e.getCause());
+            throw new IllegalStateException("Unexpected error translating " + bindingValue, e);
+        }
     }
 }