2 * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
10 import static com.google.common.base.Verify.verify;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
14 import com.google.common.base.MoreObjects.ToStringHelper;
15 import com.google.common.base.Supplier;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.ImmutableMap;
18 import java.lang.reflect.Method;
19 import java.lang.reflect.Modifier;
20 import java.util.List;
21 import java.util.Map.Entry;
22 import java.util.Optional;
23 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
24 import javassist.CannotCompileException;
25 import javassist.CtClass;
26 import javassist.CtField;
27 import javassist.CtMethod;
28 import javassist.NotFoundException;
29 import org.eclipse.jdt.annotation.NonNull;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
32 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.Customizer;
33 import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
34 import org.opendaylight.yangtools.yang.binding.DataObject;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * Private support for generating AbstractDataObject specializations.
41 final class CodecDataObjectCustomizer implements Customizer {
42 private static final Logger LOG = LoggerFactory.getLogger(CodecDataObjectCustomizer.class);
43 private static final CtClass CT_ARFU = StaticClassPool.findClass(AtomicReferenceFieldUpdater.class);
44 private static final CtClass CT_BOOLEAN = StaticClassPool.findClass(boolean.class);
45 private static final CtClass CT_DATAOBJECT = StaticClassPool.findClass(DataObject.class);
46 private static final CtClass CT_HELPER = StaticClassPool.findClass(ToStringHelper.class);
47 private static final CtClass CT_IIC = StaticClassPool.findClass(IdentifiableItemCodec.class);
48 private static final CtClass CT_INT = StaticClassPool.findClass(int.class);
49 private static final CtClass CT_NCS = StaticClassPool.findClass(NodeContextSupplier.class);
50 private static final CtClass CT_OBJECT = StaticClassPool.findClass(Object.class);
51 private static final CtClass[] EMPTY_ARGS = new CtClass[0];
52 private static final CtClass[] EQUALS_ARGS = new CtClass[] { CT_DATAOBJECT };
53 private static final CtClass[] TOSTRING_ARGS = new CtClass[] { CT_HELPER };
55 private final ImmutableMap<Method, NodeContextSupplier> properties;
56 private final Entry<Method, IdentifiableItemCodec> keyMethod;
58 CodecDataObjectCustomizer(final ImmutableMap<Method, NodeContextSupplier> properties,
59 final @Nullable Entry<Method, IdentifiableItemCodec> keyMethod) {
60 this.properties = requireNonNull(properties);
61 this.keyMethod = keyMethod;
65 public List<Class<?>> customize(final CodecClassLoader loader, final CtClass bindingClass, final CtClass generated)
66 throws NotFoundException, CannotCompileException {
67 // Generate members for all methods ...
68 LOG.trace("Generating class {}", generated.getName());
69 generated.addInterface(bindingClass);
71 for (Method method : properties.keySet()) {
72 generateMethod(loader, generated, method, CT_NCS, "resolve");
74 if (keyMethod != null) {
75 generateMethod(loader, generated, keyMethod.getKey(), CT_IIC, "resolveKey");
78 // Final bits: codecHashCode() ...
79 final CtMethod codecHashCode = new CtMethod(CT_INT, "codecHashCode", EMPTY_ARGS, generated);
80 codecHashCode.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
81 codecHashCode.setBody(codecHashCodeBody());
82 generated.addMethod(codecHashCode);
85 final CtMethod codecEquals = new CtMethod(CT_BOOLEAN, "codecEquals", EQUALS_ARGS, generated);
86 codecEquals.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
87 codecEquals.setBody(codecEqualsBody(bindingClass.getName()));
88 generated.addMethod(codecEquals);
90 // ... and codecFillToString()
91 final CtMethod codecFillToString = new CtMethod(CT_HELPER, "codecFillToString", TOSTRING_ARGS, generated);
92 codecFillToString.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
93 codecFillToString.setBody(codecFillToStringBody());
94 generated.addMethod(codecFillToString);
96 generated.setModifiers(Modifier.FINAL | Modifier.PUBLIC);
97 return ImmutableList.of();
101 public Class<?> customizeLoading(final @NonNull Supplier<Class<?>> loader) {
102 final CodecDataObjectCustomizer prev = CodecDataObjectBridge.setup(this);
104 final Class<?> result = loader.get();
107 * This a bit of magic to support NodeContextSupplier constants. These constants need to be resolved while
108 * we have the information needed to find them -- that information is being held in this instance and we
109 * leak it to a thread-local variable held by CodecDataObjectBridge.
111 * By default the JVM will defer class initialization to first use, which unfortunately is too late for
112 * us, and hence we need to force class to initialize.
115 Class.forName(result.getName(), true, result.getClassLoader());
116 } catch (ClassNotFoundException e) {
117 throw new LinkageError("Failed to find newly-defined " + result, e);
122 CodecDataObjectBridge.tearDown(prev);
127 @NonNull NodeContextSupplier resolve(final @NonNull String methodName) {
128 final Optional<Entry<Method, NodeContextSupplier>> found = properties.entrySet().stream()
129 .filter(entry -> methodName.equals(entry.getKey().getName())).findAny();
130 verify(found.isPresent(), "Failed to find property for %s in %s", methodName, this);
131 return verifyNotNull(found.get().getValue());
134 @NonNull IdentifiableItemCodec resolveKey(final @NonNull String methodName) {
135 return verifyNotNull(verifyNotNull(keyMethod, "No key method attached for %s in %s", methodName, this)
139 private String codecHashCodeBody() {
140 final StringBuilder sb = new StringBuilder()
142 .append("final int prime = 31;\n")
143 .append("int result = 1;\n");
145 for (Method method : properties.keySet()) {
146 sb.append("result = prime * result + java.util.").append(utilClass(method)).append(".hashCode(")
147 .append(method.getName()).append("());\n");
150 return sb.append("return result;\n")
151 .append('}').toString();
154 private String codecEqualsBody(final String ifaceName) {
155 final StringBuilder sb = new StringBuilder()
157 .append("final ").append(ifaceName).append(" other = $1;")
158 .append("return true");
160 for (Method method : properties.keySet()) {
161 final String methodName = method.getName();
162 sb.append("\n&& java.util.").append(utilClass(method)).append(".equals(").append(methodName)
163 .append("(), other.").append(methodName).append("())");
166 return sb.append(";\n")
167 .append('}').toString();
170 private String codecFillToStringBody() {
171 final StringBuilder sb = new StringBuilder()
173 .append("return $1");
174 for (Method method : properties.keySet()) {
175 final String methodName = method.getName();
176 sb.append("\n.add(\"").append(methodName).append("\", ").append(methodName).append("())");
179 return sb.append(";\n")
180 .append('}').toString();
183 private static void generateMethod(final CodecClassLoader loader, final CtClass generated, final Method method,
184 final CtClass contextType, final String resolveMethod) throws CannotCompileException, NotFoundException {
185 LOG.trace("Generating for method {}", method);
186 final String methodName = method.getName();
187 final String methodArfu = methodName + "$$$ARFU";
188 final String methodNcs = methodName + "$$$NCS";
190 // NodeContextSupplier ...
191 final CtField ncsField = new CtField(contextType, methodNcs, generated);
192 ncsField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
193 generated.addField(ncsField, new StringBuilder().append(CodecDataObjectBridge.class.getName())
194 .append('.').append(resolveMethod).append("(\"").append(methodName).append("\")").toString());
196 // ... AtomicReferenceFieldUpdater ...
197 final CtField arfuField = new CtField(CT_ARFU, methodArfu, generated);
198 arfuField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
199 generated.addField(arfuField, new StringBuilder().append(CT_ARFU.getName()).append(".newUpdater(")
200 .append(generated.getName()).append(".class, java.lang.Object.class, \"").append(methodName)
201 .append("\")").toString());
203 // ... corresponding volatile field ...
204 final CtField field = new CtField(CT_OBJECT, methodName, generated);
205 field.setModifiers(Modifier.PRIVATE | Modifier.VOLATILE);
206 generated.addField(field);
208 // ... and the getter
209 final Class<?> retType = method.getReturnType();
210 final CtMethod getter = new CtMethod(loader.findClass(retType), methodName, EMPTY_ARGS, generated);
211 final String retName = retType.isArray() ? retType.getSimpleName() : retType.getName();
213 getter.setBody(new StringBuilder()
215 .append("return (").append(retName).append(") codecMember(").append(methodArfu).append(", ")
216 .append(methodNcs).append(");\n")
217 .append('}').toString());
218 getter.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
219 generated.addMethod(getter);
222 private static String utilClass(final Method method) {
223 // We can either have objects or byte[], we cannot have Object[]
224 return method.getReturnType().isArray() ? "Arrays" : "Objects";