--- /dev/null
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.dom.codec.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.collect.ImmutableClassToInstanceMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.dom.codec.util.AugmentationReader;
+import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
+import org.opendaylight.yangtools.yang.binding.Augmentable;
+import org.opendaylight.yangtools.yang.binding.Augmentation;
+import org.opendaylight.yangtools.yang.binding.AugmentationHolder;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+
+/**
+ * A base class for {@link DataObject}s which are also {@link Augmentable}, backed by {@link DataObjectCodecContext}.
+ * While this class is public, it not part of API surface and is an implementation detail. The only reason for it being
+ * public is that it needs to be accessible by code generated at runtime.
+ *
+ * @param <T> DataObject type
+ */
+public abstract class AugmentableCodecDataObject<T extends DataObject & Augmentable<T>>
+ extends CodecDataObject<T> implements Augmentable<T>, AugmentationHolder<T> {
+ @SuppressWarnings("rawtypes")
+ private static final AtomicReferenceFieldUpdater<AugmentableCodecDataObject, ImmutableClassToInstanceMap>
+ CACHED_AUGMENTATIONS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(AugmentableCodecDataObject.class,
+ ImmutableClassToInstanceMap.class, "cachedAugmentations");
+ private volatile ImmutableClassToInstanceMap<Augmentation<T>> cachedAugmentations;
+
+ public AugmentableCodecDataObject(final DataObjectCodecContext<T, ?> ctx,
+ final NormalizedNodeContainer<?, ?, ?> data) {
+ super(ctx, data);
+ }
+
+ @Override
+ public final <A extends Augmentation<T>> @Nullable A augmentation(final Class<A> augmentationType) {
+ requireNonNull(augmentationType, "Supplied augmentation must not be null.");
+
+ final ImmutableClassToInstanceMap<Augmentation<T>> aug = cachedAugmentations;
+ if (aug != null) {
+ return aug.getInstance(augmentationType);
+ }
+
+ @SuppressWarnings({"unchecked","rawtypes"})
+ final Optional<DataContainerCodecContext<?, ?>> optAugCtx = context.possibleStreamChild(
+ (Class) augmentationType);
+ if (optAugCtx.isPresent()) {
+ final DataContainerCodecContext<?, ?> augCtx = optAugCtx.get();
+ // Due to binding specification not representing grouping instantiations we can end up having the same
+ // augmentation applied to a grouping multiple times. While these augmentations have the same shape, they
+ // are still represented by distinct binding classes and therefore we need to make sure the result matches
+ // the augmentation the user is requesting -- otherwise a strict receiver would end up with a cryptic
+ // ClassCastException.
+ if (augmentationType.isAssignableFrom(augCtx.getBindingClass())) {
+ final Optional<NormalizedNode<?, ?>> augData = codecData().getChild(augCtx.getDomPathArgument());
+ if (augData.isPresent()) {
+ return (A) augCtx.deserialize(augData.get());
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public final Map<Class<? extends Augmentation<T>>, Augmentation<T>> augmentations() {
+ ImmutableClassToInstanceMap<Augmentation<T>> local = cachedAugmentations;
+ if (local != null) {
+ return local;
+ }
+
+ local = ImmutableClassToInstanceMap.copyOf(context.getAllAugmentationsFrom(codecData()));
+ return CACHED_AUGMENTATIONS_UPDATER.compareAndSet(this, null, local) ? local : cachedAugmentations;
+ }
+
+ @Override
+ final int codecAugmentedHashCode() {
+ return 31 * super.codecAugmentedHashCode() + augmentations().hashCode();
+ }
+
+ @Override
+ final boolean codecAugmentedEquals(final T other) {
+ return super.codecAugmentedEquals(other) && augmentations().equals(getAllAugmentations(other));
+ }
+
+ @Override
+ final ToStringHelper codecAugmentedFillToString(final ToStringHelper helper) {
+ return super.codecAugmentedFillToString(helper).add("augmentations", augmentations());
+ }
+
+ private static Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAllAugmentations(
+ final Augmentable<?> dataObject) {
+ if (dataObject instanceof AugmentationReader) {
+ return ((AugmentationReader) dataObject).getAugmentations(dataObject);
+ }
+ return BindingReflections.getAugmentations(dataObject);
+ }
+}
final class BindingCodecContext implements CodecContextFactory, BindingCodecTree, Immutable {
private static final Logger LOG = LoggerFactory.getLogger(BindingCodecContext.class);
- private final CodecClassLoader loader = StaticClassPool.createLoader();
+ private final @NonNull CodecClassLoader loader = StaticClassPool.createLoader();
private final InstanceIdentifierCodec instanceIdentifierCodec;
private final IdentityCodec identityCodec;
private final BindingNormalizedNodeCodecRegistry registry;
return context;
}
+ @Override
+ public CodecClassLoader getLoader() {
+ return loader;
+ }
+
InstanceIdentifierCodec getInstanceIdentifierCodec() {
return instanceIdentifierCodec;
}
--- /dev/null
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.dom.codec.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+
+/**
+ * A base class for {@link DataObject}s backed by {@link DataObjectCodecContext}. While this class is public, it not
+ * part of API surface and is an implementation detail. The only reason for it being public is that it needs to be
+ * accessible by code generated at runtime.
+ *
+ * @param <T> DataObject type
+ */
+public abstract class CodecDataObject<T extends DataObject> implements DataObject {
+ private static final @NonNull Object NULL_VALUE = new Object();
+
+ @SuppressWarnings("rawtypes")
+ private final @NonNull NormalizedNodeContainer data;
+ final @NonNull DataObjectCodecContext<T, ?> context;
+
+ private volatile Integer cachedHashcode = null;
+
+ public CodecDataObject(final DataObjectCodecContext<T, ?> ctx, final NormalizedNodeContainer<?, ?, ?> data) {
+ this.context = requireNonNull(ctx, "Context must not be null");
+ this.data = requireNonNull(data, "Data must not be null");
+ }
+
+ @Override
+ public final int hashCode() {
+ final Integer cached = cachedHashcode;
+ if (cached != null) {
+ return cached;
+ }
+
+ final int result = codecAugmentedHashCode();
+ cachedHashcode = result;
+ return result;
+ }
+
+ @Override
+ public final boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ final Class<? extends DataObject> iface = implementedInterface();
+ if (!iface.isInstance(obj)) {
+ return false;
+ }
+ @SuppressWarnings("unchecked")
+ final T other = (T) iface.cast(obj);
+ if (other instanceof CodecDataObject) {
+ return data.equals(((CodecDataObject<?>) obj).data);
+ }
+ return codecAugmentedEquals(other);
+ }
+
+ @Override
+ public final String toString() {
+ return codecAugmentedFillToString(MoreObjects.toStringHelper(implementedInterface()).omitNullValues())
+ .toString();
+ }
+
+ // TODO: consider switching to VarHandles for Java 9+
+ protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
+ final String methodName) {
+ final Object cached = updater.get(this);
+ return cached != null ? unmaskNull(cached) : loadMember(updater, methodName);
+ }
+
+ protected abstract int codecHashCode();
+
+ protected abstract boolean codecEquals(T other);
+
+ protected abstract ToStringHelper codecFillToString(ToStringHelper helper);
+
+ @SuppressWarnings("rawtypes")
+ final @NonNull NormalizedNodeContainer codecData() {
+ return data;
+ }
+
+ // Non-final to allow specialization in AugmentableCodecDataObject
+ int codecAugmentedHashCode() {
+ return codecHashCode();
+ }
+
+ // Non-final to allow specialization in AugmentableCodecDataObject
+ boolean codecAugmentedEquals(final T other) {
+ return codecEquals(other);
+ }
+
+ // Non-final to allow specialization in AugmentableCodecDataObject
+ ToStringHelper codecAugmentedFillToString(final ToStringHelper helper) {
+ return codecFillToString(helper);
+ }
+
+ // Helpers split out of codecMember to aid its inlining
+ private Object loadMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
+ final String methodName) {
+ final Object decoded = context.getBindingChildValue(methodName, data);
+ return updater.compareAndSet(this, null, maskNull(decoded)) ? decoded : unmaskNull(updater.get(this));
+ }
+
+ private static @NonNull Object maskNull(final @Nullable Object unmasked) {
+ return unmasked == null ? NULL_VALUE : unmasked;
+ }
+
+ private static @Nullable Object unmaskNull(final Object masked) {
+ return masked == NULL_VALUE ? null : masked;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.dom.codec.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import javassist.CannotCompileException;
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.CtMethod;
+import javassist.NotFoundException;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.Customizer;
+import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Private support for generating AbstractDataObject specializations.
+ */
+final class CodecDataObjectCustomizer implements Customizer {
+ private static final Logger LOG = LoggerFactory.getLogger(CodecDataObjectCustomizer.class);
+ private static final CtClass CT_ARFU = StaticClassPool.findClass(AtomicReferenceFieldUpdater.class);
+ private static final CtClass CT_BOOLEAN = StaticClassPool.findClass(boolean.class);
+ private static final CtClass CT_INT = StaticClassPool.findClass(int.class);
+ private static final CtClass CT_OBJECT = StaticClassPool.findClass(Object.class);
+ private static final CtClass CT_HELPER = StaticClassPool.findClass(ToStringHelper.class);
+ private static final CtClass CT_DATAOBJECT = StaticClassPool.findClass(DataObject.class);
+ private static final CtClass[] EMPTY_ARGS = new CtClass[0];
+ private static final CtClass[] EQUALS_ARGS = new CtClass[] { CT_DATAOBJECT };
+ private static final CtClass[] TOSTRING_ARGS = new CtClass[] { CT_HELPER };
+
+ private final List<Method> properties;
+ private final List<Method> methods;
+
+ CodecDataObjectCustomizer(final List<Method> properties, final List<Method> methods) {
+ this.properties = requireNonNull(properties);
+ this.methods = requireNonNull(methods);
+ }
+
+ @Override
+ public List<Class<?>> customize(final CodecClassLoader loader, final CtClass bindingClass, final CtClass generated)
+ throws NotFoundException, CannotCompileException {
+ final String classFqn = generated.getName();
+ generated.addInterface(bindingClass);
+
+ // Generate members for all methods ...
+ LOG.trace("Generating class {}", classFqn);
+ for (Method method : methods) {
+ LOG.trace("Generating for method {}", method);
+ final String methodName = method.getName();
+ final String methodArfu = methodName + "$$$ARFU";
+
+ // AtomicReferenceFieldUpdater ...
+ final CtField arfuField = new CtField(CT_ARFU, methodArfu, generated);
+ arfuField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
+ generated.addField(arfuField, new StringBuilder().append(CT_ARFU.getName()).append(".newUpdater(")
+ .append(generated.getName()).append(".class, java.lang.Object.class, \"").append(methodName)
+ .append("\")").toString());
+
+ // ... corresponding volatile field ...
+ final CtField field = new CtField(CT_OBJECT, methodName, generated);
+ field.setModifiers(Modifier.PRIVATE | Modifier.VOLATILE);
+ generated.addField(field);
+
+ // ... and the getter
+ final Class<?> retType = method.getReturnType();
+ final CtMethod getter = new CtMethod(loader.findClass(retType), methodName, EMPTY_ARGS, generated);
+ final String retName = retType.isArray() ? retType.getSimpleName() : retType.getName();
+
+ getter.setBody(new StringBuilder()
+ .append("{\n")
+ .append("return (").append(retName).append(") codecMember(").append(methodArfu).append(", \"")
+ .append(methodName).append("\");\n")
+ .append('}').toString());
+ getter.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
+ generated.addMethod(getter);
+ }
+
+ // Final bits: codecHashCode() ...
+ final CtMethod codecHashCode = new CtMethod(CT_INT, "codecHashCode", EMPTY_ARGS, generated);
+ codecHashCode.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
+ codecHashCode.setBody(codecHashCodeBody());
+ generated.addMethod(codecHashCode);
+
+ // ... equals
+ final CtMethod codecEquals = new CtMethod(CT_BOOLEAN, "codecEquals", EQUALS_ARGS, generated);
+ codecEquals.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
+ codecEquals.setBody(codecEqualsBody(bindingClass.getName()));
+ generated.addMethod(codecEquals);
+
+ // ... and codecFillToString()
+ final CtMethod codecFillToString = new CtMethod(CT_HELPER, "codecFillToString", TOSTRING_ARGS, generated);
+ codecFillToString.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
+ codecFillToString.setBody(codecFillToStringBody());
+ generated.addMethod(codecFillToString);
+
+ generated.setModifiers(Modifier.FINAL | Modifier.PUBLIC);
+ return ImmutableList.of();
+ }
+
+ private String codecHashCodeBody() {
+ final StringBuilder sb = new StringBuilder()
+ .append("{\n")
+ .append("final int prime = 31;\n")
+ .append("int result = 1;\n");
+
+ for (Method method : properties) {
+ sb.append("result = prime * result + java.util.").append(utilClass(method)).append(".hashCode(")
+ .append(method.getName()).append("());\n");
+ }
+
+ return sb.append("return result;\n")
+ .append('}').toString();
+ }
+
+ private String codecEqualsBody(final String ifaceName) {
+ final StringBuilder sb = new StringBuilder()
+ .append("{\n")
+ .append("final ").append(ifaceName).append(" other = $1;")
+ .append("return true");
+
+ for (Method method : properties) {
+ final String methodName = method.getName();
+ sb.append("\n&& java.util.").append(utilClass(method)).append(".equals(").append(methodName)
+ .append("(), other.").append(methodName).append("())");
+ }
+
+ return sb.append(";\n")
+ .append('}').toString();
+ }
+
+ private String codecFillToStringBody() {
+ final StringBuilder sb = new StringBuilder()
+ .append("{\n")
+ .append("return $1");
+ for (Method method : properties) {
+ final String methodName = method.getName();
+ sb.append("\n.add(\"").append(methodName).append("\", ").append(methodName).append("())");
+ }
+
+ return sb.append(";\n")
+ .append('}').toString();
+ }
+
+ private static String utilClass(final Method method) {
+ // We can either have objects or byte[], we cannot have Object[]
+ return method.getReturnType().isArray() ? "Arrays" : "Objects";
+ }
+}
\ No newline at end of file
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
+import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
-import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import javassist.CannotCompileException;
+import javassist.CtClass;
+import javassist.NotFoundException;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
import org.opendaylight.mdsal.binding.generator.api.ClassLoadingStrategy;
import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
import org.opendaylight.mdsal.binding.model.api.Type;
-import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
import org.opendaylight.yangtools.concepts.Immutable;
import org.opendaylight.yangtools.util.ClassLoaderUtils;
import org.opendaylight.yangtools.yang.binding.Augmentable;
import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.AugmentationHolder;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
}
private static final Logger LOG = LoggerFactory.getLogger(DataObjectCodecContext.class);
- private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, InvocationHandler.class);
- private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class, InvocationHandler.class);
+ private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, DataObjectCodecContext.class,
+ NormalizedNodeContainer.class);
+ private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class,
+ DataObjectCodecContext.class, NormalizedNodeContainer.class);
private static final Comparator<Method> METHOD_BY_ALPHABET = Comparator.comparing(Method::getName);
private static final Augmentations EMPTY_AUGMENTATIONS = new Augmentations(ImmutableMap.of(), ImmutableMap.of());
- private static final Method[] EMPTY_METHODS = new Method[0];
+ private static final CtClass SUPERCLASS = StaticClassPool.findClass(CodecDataObject.class);
+ private static final CtClass AUGMENTABLE_SUPERCLASS = StaticClassPool.findClass(
+ AugmentableCodecDataObject.class);
private final ImmutableMap<String, ValueNodeCodecContext> leafChild;
private final ImmutableMap<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYang;
private final ImmutableMap<String, NodeContextSupplier> byMethod;
- private final ImmutableMap<String, String> nonnullToGetter;
private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byStreamClass;
private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClass;
private final ImmutableMap<AugmentationIdentifier, Type> possibleAugmentations;
private final MethodHandle proxyConstructor;
- private final Method[] propertyMethods;
@SuppressWarnings("rawtypes")
private static final AtomicReferenceFieldUpdater<DataObjectCodecContext, Augmentations>
private volatile ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> mismatchedAugmented = ImmutableMap.of();
- DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype) {
+ DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype, final Method... additionalMethods) {
super(prototype);
final Class<D> bindingClass = getBindingClass();
final int methodCount = tmpMethodToSupplier.size();
final Builder<String, NodeContextSupplier> byMethodBuilder = ImmutableMap.builderWithExpectedSize(methodCount);
- this.propertyMethods = methodCount == 0 ? EMPTY_METHODS : new Method[methodCount];
- int offset = 0;
+
+ final List<Method> properties = new ArrayList<>(methodCount);
for (Entry<Method, NodeContextSupplier> entry : tmpMethodToSupplier.entrySet()) {
final Method method = entry.getKey();
- propertyMethods[offset++] = method;
+ properties.add(method);
byMethodBuilder.put(method.getName(), entry.getValue());
}
// Make sure properties are alpha-sorted
- Arrays.sort(propertyMethods, METHOD_BY_ALPHABET);
+ properties.sort(METHOD_BY_ALPHABET);
this.byMethod = byMethodBuilder.build();
this.byYang = ImmutableMap.copyOf(byYangBuilder);
byBindingArgClassBuilder.putAll(byStreamClass);
this.byBindingArgClass = ImmutableMap.copyOf(byBindingArgClassBuilder);
- final Map<Class<?>, Method> clsToNonnull = BindingReflections.getChildrenClassToNonnullMethod(bindingClass);
- final Map<String, String> nonnullToGetterBuilder = new HashMap<>();
- for (final Entry<Class<?>, Method> entry : clsToNonnull.entrySet()) {
- final Method method = entry.getValue();
- if (!method.isDefault()) {
- LOG.warn("Ignoring non-default method {} in {}", method, bindingClass);
- continue;
- }
-
- // Derive getter name from the nonnull method and verify we have the corresponding getter. Note that
- // the intern() call is important, as it makes sure we use the same instance to bridge to byMethod map.
- final String methodName = method.getName();
- final String getterName = BindingMapping.getGetterMethodForNonnull(methodName).intern();
- verify(byMethod.containsKey(getterName), "Cannot find getter %s for %s", getterName, methodName);
- nonnullToGetterBuilder.put(methodName, getterName);
- }
- nonnullToGetter = ImmutableMap.copyOf(nonnullToGetterBuilder);
-
+ final CtClass superClass;
if (Augmentable.class.isAssignableFrom(bindingClass)) {
this.possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema());
+ superClass = AUGMENTABLE_SUPERCLASS;
} else {
this.possibleAugmentations = ImmutableMap.of();
+ superClass = SUPERCLASS;
}
reloadAllAugmentations();
- final Class<?> proxyClass = Proxy.getProxyClass(bindingClass.getClassLoader(), bindingClass,
- AugmentationHolder.class);
+ final List<Method> methods;
+ if (additionalMethods.length != 0) {
+ methods = new ArrayList<>(properties.size() + 1);
+ methods.addAll(properties);
+ methods.addAll(Arrays.asList(additionalMethods));
+ } else {
+ methods = properties;
+ }
+
+ final Class<?> generatedClass;
+ try {
+ generatedClass = prototype.getFactory().getLoader().generateSubclass(superClass, bindingClass, "codecImpl",
+ new CodecDataObjectCustomizer(properties, methods));
+ } catch (CannotCompileException | IOException | NotFoundException e) {
+ throw new LinkageError("Failed to generated class for " + bindingClass, e);
+ }
+
try {
- proxyConstructor = MethodHandles.publicLookup().findConstructor(proxyClass, CONSTRUCTOR_TYPE)
- .asType(DATAOBJECT_TYPE);
+ proxyConstructor = MethodHandles.publicLookup().findConstructor(generatedClass, CONSTRUCTOR_TYPE)
+ .asType(DATAOBJECT_TYPE).bindTo(this);
} catch (NoSuchMethodException | IllegalAccessException e) {
- throw new IllegalStateException("Failed to find contructor for class " + proxyClass, e);
+ throw new LinkageError("Failed to find contructor for class " + generatedClass, e);
}
}
if (ctxProto == null && Augmentation.class.isAssignableFrom(argType)) {
ctxProto = augmentationByClass(argType);
}
- final DataContainerCodecContext<?, ?> context =
- childNonNull(ctxProto, argType, "Class %s is not valid child of %s", argType, getBindingClass()).get();
+ final DataContainerCodecContext<?, ?> context = childNonNull(ctxProto, argType,
+ "Class %s is not valid child of %s", argType, getBindingClass()).get();
if (context instanceof ChoiceNodeCodecContext) {
final ChoiceNodeCodecContext<?> choice = (ChoiceNodeCodecContext<?>) context;
choice.addYangPathArgument(arg, builder);
return DataContainerCodecPrototype.from(augClass, augSchema.getKey(), augSchema.getValue(), factory());
}
- // Unlike BindingMapping.getGetterMethodForNonnull() this returns an interned String
- @NonNull String getterNameForNonnullName(final String nonnullMethod) {
- return verifyNotNull(nonnullToGetter.get(nonnullMethod), "Failed to look up getter method for %s",
- nonnullMethod);
- }
-
@SuppressWarnings("rawtypes")
@Nullable Object getBindingChildValue(final String method, final NormalizedNodeContainer domData) {
final NodeCodecContext childContext = verifyNotNull(byMethod.get(method),
@SuppressWarnings("checkstyle:illegalCatch")
protected final D createBindingProxy(final NormalizedNodeContainer<?, ?, ?> node) {
try {
- return (D) proxyConstructor.invokeExact((InvocationHandler)new LazyDataObject<>(this, node));
+ return (D) proxyConstructor.invokeExact(node);
} catch (final Throwable e) {
Throwables.throwIfUnchecked(e);
throw new IllegalStateException(e);
return map;
}
- final Method[] propertyMethods() {
- return propertyMethods;
- }
-
@Override
public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
checkArgument(getDomPathArgument().equals(arg));
import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.IDENTIFIABLE_KEY_NAME;
+import java.lang.reflect.Method;
import java.util.List;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.Identifiable;
private final IdentifiableItemCodec codec;
KeyedListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype) {
- super(prototype);
+ super(prototype, keyMethod(prototype.getBindingClass()));
+ this.codec = factory().getPathArgumentCodec(getBindingClass(), getSchema());
+ }
- final Class<D> bindingClass = getBindingClass();
- this.codec = factory().getPathArgumentCodec(bindingClass, getSchema());
+ private static Method keyMethod(final Class<?> bindingClass) {
try {
// This just verifies the method is present
- bindingClass.getMethod(IDENTIFIABLE_KEY_NAME);
+ return bindingClass.getMethod(IDENTIFIABLE_KEY_NAME);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Required method not available", e);
}
+++ /dev/null
-/*
- * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.mdsal.binding.dom.codec.impl;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.Objects.requireNonNull;
-import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME;
-import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.binding.dom.codec.util.AugmentationReader;
-import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
-import org.opendaylight.yangtools.yang.binding.Augmentable;
-import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class LazyDataObject<D extends DataObject> implements InvocationHandler, AugmentationReader {
-
- private static final Logger LOG = LoggerFactory.getLogger(LazyDataObject.class);
- private static final String TO_STRING = "toString";
- private static final String EQUALS = "equals";
- private static final String HASHCODE = "hashCode";
- private static final String AUGMENTATIONS = "augmentations";
- private static final @NonNull Object NULL_VALUE = new Object();
-
- // Method.getName() is guaranteed to be interned and all getter methods have zero arguments, name is sufficient to
- // identify the data, skipping Method.hashCode() computation.
- private final ConcurrentHashMap<String, Object> cachedData = new ConcurrentHashMap<>();
- private final NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> data;
- private final DataObjectCodecContext<D,?> context;
-
- @SuppressWarnings("rawtypes")
- private static final AtomicReferenceFieldUpdater<LazyDataObject, ImmutableMap> CACHED_AUGMENTATIONS_UPDATER =
- AtomicReferenceFieldUpdater.newUpdater(LazyDataObject.class, ImmutableMap.class, "cachedAugmentations");
- private volatile ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> cachedAugmentations = null;
- private volatile Integer cachedHashcode = null;
-
- @SuppressWarnings({ "rawtypes", "unchecked" })
- LazyDataObject(final DataObjectCodecContext<D,?> ctx, final NormalizedNodeContainer data) {
- this.context = requireNonNull(ctx, "Context must not be null");
- this.data = requireNonNull(data, "Data must not be null");
- }
-
- @Override
- public Object invoke(final Object proxy, final Method method, final Object[] args) {
- switch (method.getParameterCount()) {
- case 0:
- final String methodName = method.getName();
- switch (methodName) {
- case DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME:
- return context.getBindingClass();
- case TO_STRING:
- return bindingToString();
- case HASHCODE:
- return bindingHashCode();
- case AUGMENTATIONS:
- return getAugmentationsImpl();
- default:
- return method.isDefault() ? nonnullBindingData(methodName) : getBindingData(methodName);
- }
- case 1:
- switch (method.getName()) {
- case AUGMENTABLE_AUGMENTATION_NAME:
- return getAugmentationImpl((Class<?>) args[0]);
- case EQUALS:
- return bindingEquals(args[0]);
- default:
- break;
- }
- break;
- default:
- break;
- }
-
- throw new UnsupportedOperationException("Unsupported method " + method);
- }
-
- private boolean bindingEquals(final Object other) {
- if (other == null) {
- return false;
- }
- final Class<D> bindingClass = context.getBindingClass();
- if (!bindingClass.isAssignableFrom(other.getClass())) {
- return false;
- }
- try {
- for (final Method m : context.propertyMethods()) {
- final Object thisValue = getBindingData(m.getName());
- final Object otherValue = m.invoke(other);
- /*
- * added for valid byte array comparison, when list key type is binary
- * deepEquals is not used since it does excessive amount of instanceof calls.
- */
- if (thisValue instanceof byte[] && otherValue instanceof byte[]) {
- if (!Arrays.equals((byte[]) thisValue, (byte[]) otherValue)) {
- return false;
- }
- } else if (!Objects.equals(thisValue, otherValue)) {
- return false;
- }
- }
-
- if (Augmentable.class.isAssignableFrom(bindingClass)) {
- if (!getAugmentationsImpl().equals(getAllAugmentations(other))) {
- return false;
- }
- }
- } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
- LOG.warn("Can not determine equality of {} and {}", this, other, e);
- return false;
- }
- return true;
- }
-
- private static Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAllAugmentations(final Object dataObject) {
- if (dataObject instanceof AugmentationReader) {
- return ((AugmentationReader) dataObject).getAugmentations(dataObject);
- } else if (dataObject instanceof Augmentable<?>) {
- return BindingReflections.getAugmentations((Augmentable<?>) dataObject);
- }
-
- throw new IllegalArgumentException("Unable to get all augmentations from " + dataObject);
- }
-
- private Integer bindingHashCode() {
- final Integer cached = cachedHashcode;
- if (cached != null) {
- return cached;
- }
-
- final int prime = 31;
- int result = 1;
- for (final Method m : context.propertyMethods()) {
- final Object value = getBindingData(m.getName());
- result = prime * result + Objects.hashCode(value);
- }
- if (Augmentable.class.isAssignableFrom(context.getBindingClass())) {
- result = prime * result + getAugmentationsImpl().hashCode();
- }
- final Integer ret = result;
- cachedHashcode = ret;
- return ret;
- }
-
- private Object nonnullBindingData(final String methodName) {
- final Object value = getBindingData(context.getterNameForNonnullName(methodName));
- return value != null ? value : ImmutableList.of();
- }
-
- // Internal invocation, can only target getFoo() methods
- private Object getBindingData(final String methodName) {
- final Object cached = cachedData.get(methodName);
- if (cached != null) {
- return unmaskNull(cached);
- }
-
- final Object value = context.getBindingChildValue(methodName, data);
- final Object raced = cachedData.putIfAbsent(methodName, value == null ? NULL_VALUE : value);
- // If we raced we need to return previously-stored value
- return raced != null ? unmaskNull(raced) : value;
- }
-
- private static Object unmaskNull(final @NonNull Object masked) {
- return masked == NULL_VALUE ? null : masked;
- }
-
- private Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentationsImpl() {
- ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> local = cachedAugmentations;
- if (local != null) {
- return local;
- }
-
- local = ImmutableMap.copyOf(context.getAllAugmentationsFrom(data));
- return CACHED_AUGMENTATIONS_UPDATER.compareAndSet(this, null, local) ? local : cachedAugmentations;
- }
-
- @Override
- public Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentations(final Object obj) {
- checkArgument(this == Proxy.getInvocationHandler(obj),
- "Supplied object is not associated with this proxy handler");
-
- return getAugmentationsImpl();
- }
-
- private Object getAugmentationImpl(final Class<?> cls) {
- requireNonNull(cls, "Supplied augmentation must not be null.");
-
- final ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> aug = cachedAugmentations;
- if (aug != null) {
- return aug.get(cls);
- }
-
- @SuppressWarnings({"unchecked","rawtypes"})
- final Optional<DataContainerCodecContext<?, ?>> optAugCtx = context.possibleStreamChild((Class) cls);
- if (optAugCtx.isPresent()) {
- final DataContainerCodecContext<?, ?> augCtx = optAugCtx.get();
- // Due to binding specification not representing grouping instantiations we can end up having the same
- // augmentation applied to a grouping multiple times. While these augmentations have the same shape, they
- // are still represented by distinct binding classes and therefore we need to make sure the result matches
- // the augmentation the user is requesting -- otherwise a strict receiver would end up with a cryptic
- // ClassCastException.
- if (cls.isAssignableFrom(augCtx.getBindingClass())) {
- final Optional<NormalizedNode<?, ?>> augData = data.getChild(augCtx.getDomPathArgument());
- if (augData.isPresent()) {
- return augCtx.deserialize(augData.get());
- }
- }
- }
- return null;
- }
-
- public String bindingToString() {
- final Class<D> bindingClass = context.getBindingClass();
- final ToStringHelper helper = MoreObjects.toStringHelper(bindingClass).omitNullValues();
-
- for (final Method m : context.propertyMethods()) {
- final String methodName = m.getName();
- helper.add(methodName, getBindingData(methodName));
- }
- if (Augmentable.class.isAssignableFrom(bindingClass)) {
- helper.add("augmentations", getAugmentationsImpl());
- }
- return helper.toString();
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + context.hashCode();
- result = prime * result + data.hashCode();
- return result;
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- final LazyDataObject<?> other = (LazyDataObject<?>) obj;
- return Objects.equals(context, other.context) && Objects.equals(data, other.data);
- }
-}
*/
package org.opendaylight.mdsal.binding.dom.codec.impl;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
class ListNodeCodecContext<D extends DataObject> extends DataObjectCodecContext<D,ListSchemaNode> {
- protected ListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype) {
- super(prototype);
+ protected ListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype,
+ final Method... additionalMethods) {
+ super(prototype, additionalMethods);
}
@Override
import com.google.common.collect.ImmutableMap;
import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
import org.opendaylight.yangtools.yang.binding.DataObjectSerializer;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
*/
IdentifiableItemCodec getPathArgumentCodec(Class<?> type, ListSchemaNode schema);
+ /**
+ * Return the codec loader associated with this factory.
+ *
+ * @return A codec loader instance
+ */
+ @NonNull CodecClassLoader getLoader();
+
DataObjectSerializer getEventStreamSerializer(Class<?> type);
}