Generate DataObject codec implementation
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / CodecDataObjectCustomizer.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.MoreObjects.ToStringHelper;
13 import com.google.common.collect.ImmutableList;
14 import java.lang.reflect.Method;
15 import java.lang.reflect.Modifier;
16 import java.util.List;
17 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
18 import javassist.CannotCompileException;
19 import javassist.CtClass;
20 import javassist.CtField;
21 import javassist.CtMethod;
22 import javassist.NotFoundException;
23 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
24 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.Customizer;
25 import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
26 import org.opendaylight.yangtools.yang.binding.DataObject;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * Private support for generating AbstractDataObject specializations.
32  */
33 final class CodecDataObjectCustomizer implements Customizer {
34     private static final Logger LOG = LoggerFactory.getLogger(CodecDataObjectCustomizer.class);
35     private static final CtClass CT_ARFU = StaticClassPool.findClass(AtomicReferenceFieldUpdater.class);
36     private static final CtClass CT_BOOLEAN = StaticClassPool.findClass(boolean.class);
37     private static final CtClass CT_INT = StaticClassPool.findClass(int.class);
38     private static final CtClass CT_OBJECT = StaticClassPool.findClass(Object.class);
39     private static final CtClass CT_HELPER = StaticClassPool.findClass(ToStringHelper.class);
40     private static final CtClass CT_DATAOBJECT = StaticClassPool.findClass(DataObject.class);
41     private static final CtClass[] EMPTY_ARGS = new CtClass[0];
42     private static final CtClass[] EQUALS_ARGS = new CtClass[] { CT_DATAOBJECT };
43     private static final CtClass[] TOSTRING_ARGS = new CtClass[] { CT_HELPER };
44
45     private final List<Method> properties;
46     private final List<Method> methods;
47
48     CodecDataObjectCustomizer(final List<Method> properties, final List<Method> methods) {
49         this.properties = requireNonNull(properties);
50         this.methods = requireNonNull(methods);
51     }
52
53     @Override
54     public List<Class<?>> customize(final CodecClassLoader loader, final CtClass bindingClass, final CtClass generated)
55             throws NotFoundException, CannotCompileException {
56         final String classFqn = generated.getName();
57         generated.addInterface(bindingClass);
58
59         // Generate members for all methods ...
60         LOG.trace("Generating class {}", classFqn);
61         for (Method method : methods) {
62             LOG.trace("Generating for method {}", method);
63             final String methodName = method.getName();
64             final String methodArfu = methodName + "$$$ARFU";
65
66             // AtomicReferenceFieldUpdater ...
67             final CtField arfuField = new CtField(CT_ARFU, methodArfu, generated);
68             arfuField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
69             generated.addField(arfuField, new StringBuilder().append(CT_ARFU.getName()).append(".newUpdater(")
70                 .append(generated.getName()).append(".class, java.lang.Object.class, \"").append(methodName)
71                 .append("\")").toString());
72
73             // ... corresponding volatile field ...
74             final CtField field = new CtField(CT_OBJECT, methodName, generated);
75             field.setModifiers(Modifier.PRIVATE | Modifier.VOLATILE);
76             generated.addField(field);
77
78             // ... and the getter
79             final Class<?> retType = method.getReturnType();
80             final CtMethod getter = new CtMethod(loader.findClass(retType), methodName, EMPTY_ARGS, generated);
81             final String retName = retType.isArray() ? retType.getSimpleName() : retType.getName();
82
83             getter.setBody(new StringBuilder()
84                 .append("{\n")
85                 .append("return (").append(retName).append(") codecMember(").append(methodArfu).append(", \"")
86                     .append(methodName).append("\");\n")
87                 .append('}').toString());
88             getter.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
89             generated.addMethod(getter);
90         }
91
92         // Final bits: codecHashCode() ...
93         final CtMethod codecHashCode = new CtMethod(CT_INT, "codecHashCode", EMPTY_ARGS, generated);
94         codecHashCode.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
95         codecHashCode.setBody(codecHashCodeBody());
96         generated.addMethod(codecHashCode);
97
98         // ... equals
99         final CtMethod codecEquals = new CtMethod(CT_BOOLEAN, "codecEquals", EQUALS_ARGS, generated);
100         codecEquals.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
101         codecEquals.setBody(codecEqualsBody(bindingClass.getName()));
102         generated.addMethod(codecEquals);
103
104         // ... and codecFillToString()
105         final CtMethod codecFillToString = new CtMethod(CT_HELPER, "codecFillToString", TOSTRING_ARGS, generated);
106         codecFillToString.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
107         codecFillToString.setBody(codecFillToStringBody());
108         generated.addMethod(codecFillToString);
109
110         generated.setModifiers(Modifier.FINAL | Modifier.PUBLIC);
111         return ImmutableList.of();
112     }
113
114     private String codecHashCodeBody() {
115         final StringBuilder sb = new StringBuilder()
116                 .append("{\n")
117                 .append("final int prime = 31;\n")
118                 .append("int result = 1;\n");
119
120         for (Method method : properties) {
121             sb.append("result = prime * result + java.util.").append(utilClass(method)).append(".hashCode(")
122             .append(method.getName()).append("());\n");
123         }
124
125         return sb.append("return result;\n")
126                 .append('}').toString();
127     }
128
129     private String codecEqualsBody(final String ifaceName) {
130         final StringBuilder sb = new StringBuilder()
131                 .append("{\n")
132                 .append("final ").append(ifaceName).append(" other = $1;")
133                 .append("return true");
134
135         for (Method method : properties) {
136             final String methodName = method.getName();
137             sb.append("\n&& java.util.").append(utilClass(method)).append(".equals(").append(methodName)
138             .append("(), other.").append(methodName).append("())");
139         }
140
141         return sb.append(";\n")
142                 .append('}').toString();
143     }
144
145     private String codecFillToStringBody() {
146         final StringBuilder sb = new StringBuilder()
147                 .append("{\n")
148                 .append("return $1");
149         for (Method method : properties) {
150             final String methodName = method.getName();
151             sb.append("\n.add(\"").append(methodName).append("\", ").append(methodName).append("())");
152         }
153
154         return sb.append(";\n")
155                 .append('}').toString();
156     }
157
158     private static String utilClass(final Method method) {
159         // We can either have objects or byte[], we cannot have Object[]
160         return method.getReturnType().isArray() ? "Arrays" : "Objects";
161     }
162 }