d468e075c18be776a7f39e05e2998e00ece2267b
[ccsdk/features.git] / sdnr / wt / common-yang / utils / src / main / java / org / onap / ccsdk / features / sdnr / wt / yang / mapper / YangToolsMapperHelper.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP : ccsdk features
4  * ================================================================================
5  * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property.
6  * All rights reserved.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  *
21  */
22 package org.onap.ccsdk.features.sdnr.wt.yang.mapper;
23
24 import com.fasterxml.jackson.databind.DeserializationContext;
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Method;
28 import java.time.Instant;
29 import java.time.ZoneOffset;
30 import java.time.ZonedDateTime;
31 import java.time.format.DateTimeFormatter;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.Optional;
36 import java.util.concurrent.ConcurrentHashMap;
37 import javax.annotation.Nullable;
38 import org.opendaylight.mdsal.dom.api.DOMEvent;
39 import org.opendaylight.mdsal.dom.api.DOMNotification;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
41 import org.opendaylight.yangtools.concepts.Builder;
42 import org.opendaylight.yangtools.yang.binding.EventInstantAware;
43 import org.opendaylight.yangtools.yang.binding.Notification;
44 import org.osgi.framework.Bundle;
45 import org.osgi.framework.BundleContext;
46 import org.osgi.framework.FrameworkUtil;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 public class YangToolsMapperHelper {
51
52     private static final Logger LOG = LoggerFactory.getLogger(YangToolsMapperHelper.class);
53     private static final String TYPEOBJECT_INSTANCE_METHOD = "getDefaultInstance";
54     private static final String BUILDER = "Builder";
55     private static final DateTimeFormatter formatterOutput =
56             DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.S'Z'").withZone(ZoneOffset.UTC);
57
58     private static BundleContext context = getBundleContext();
59     private static ConcurrentHashMap<String, Class<?>> cache = new ConcurrentHashMap<>();
60
61     private YangToolsMapperHelper() {
62         //Make unaccessible
63     }
64
65     public static Class<?> findClass(String name) throws ClassNotFoundException {
66
67         //Try first in cache
68         Class<?> res = cache.get(name);
69         if (res != null) {
70             return res;
71         }
72         //Try first in actual bundle
73         try {
74             return loadClass(null, name);
75         } catch (ClassNotFoundException e) {
76             // No problem, this bundle doesn't have the class
77         }
78         // Try to find in other bundles
79         if (context != null) {
80             //OSGi environment
81             for (Bundle b : context.getBundles()) {
82                 try {
83                     return loadClass(b, name);
84                 } catch (ClassNotFoundException e) {
85                     // No problem, this bundle doesn't have the class
86                 }
87             }
88         }
89         // really not found in any bundle
90         throw new ClassNotFoundException("Can not find class '" + name + "'");
91     }
92
93     private static Class<?> loadClass(Bundle b, String name) throws ClassNotFoundException {
94         Class<?> res = b == null ? Class.forName(name) : b.loadClass(name);
95         cache.put(name, res);
96         return res;
97     }
98
99     /**
100      * Verify if builder is available
101      *
102      * @throws ClassNotFoundException
103      **/
104     public static Class<?> assertBuilderClass(Class<?> clazz) throws ClassNotFoundException {
105         return getBuilderClass(getBuilderClassName(clazz));
106     }
107
108     public static Class<?> getBuilderClass(String name) throws ClassNotFoundException {
109         return findClass(name);
110     }
111
112     public static Class<?> getBuilderClass(Class<?> clazz) throws ClassNotFoundException {
113         return findClass(getBuilderClassName(clazz));
114     }
115
116     /**
117      * Create name of builder class
118      *
119      * @param <T>
120      * @param clazz
121      * @return builders class name
122      * @throws ClassNotFoundException
123      */
124     public static String getBuilderClassName(Class<?> clazz) {
125         return clazz.getName() + BUILDER;
126     }
127
128     @SuppressWarnings("unchecked")
129     public static <B extends Builder<?>> Class<B> findBuilderClass(DeserializationContext ctxt, Class<?> clazz)
130             throws ClassNotFoundException {
131         return (Class<B>) findClass(getBuilderClassName(clazz));
132     }
133
134     public static <B extends Builder<?>> Optional<Class<B>> findBuilderClassOptional(DeserializationContext ctxt,
135             Class<?> clazz) {
136         try {
137             return Optional.of(findBuilderClass(ctxt, clazz));
138         } catch (ClassNotFoundException e) {
139             return Optional.empty();
140         }
141     }
142
143     public static boolean hasClassDeclaredMethod(Class<?> clazz, String name) {
144         Method[] methods = clazz.getDeclaredMethods();
145         for (Method m : methods) {
146             if (m.getName().equals(name)) {
147                 return true;
148             }
149         }
150         return false;
151     }
152
153     @SuppressWarnings("unchecked")
154     public static <T> Optional<T> getInstanceByConstructor(Class<?> clazz, String arg)
155             throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
156             NoSuchMethodException, SecurityException {
157         List<Class<?>> ctypes = getConstructorParameterTypes(clazz, String.class);
158         Optional<Object> oObj;
159         for (Class<?> ctype : ctypes) {
160             if (ctype.equals(String.class)) {
161                 return Optional.of((T) clazz.getConstructor(ctype).newInstance(arg));
162             } else if ((oObj = getDefaultInstance(ctype, arg)).isPresent()) {
163                 return Optional.of((T) clazz.getConstructor(ctype).newInstance(oObj.get()));
164             } else {
165                 // TODO: recursive instantiation down to string constructor or
166                 // getDefaultInstance method
167                 LOG.debug("Not implemented arg:'{}' class:'{}'", arg, clazz);
168             }
169         }
170         return Optional.empty();
171     }
172
173     @SuppressWarnings("unchecked")
174     public static <T> Optional<T> getDefaultInstance(@Nullable Class<?> clazz, String arg) throws NoSuchMethodException,
175             SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
176         LOG.trace("arg:'{}' clazz '{}'", arg, clazz != null ? clazz.getName() : "null");
177         if (clazz != null) {
178             Method[] methods = clazz.getDeclaredMethods();
179             for (Method m : methods) {
180                 //TODO Verify argument type to avoid exception
181                 if (m.getName().equals(TYPEOBJECT_INSTANCE_METHOD)) {
182                     Method method = clazz.getDeclaredMethod(TYPEOBJECT_INSTANCE_METHOD, String.class);
183                     LOG.trace("Invoke {} available {}", TYPEOBJECT_INSTANCE_METHOD, method != null);
184                     return Optional.of((T) method.invoke(null, arg));
185                 }
186             }
187         }
188         return Optional.empty();
189     }
190
191     public static <T> Optional<T> getDefaultInstance(Optional<Class<T>> optionalClazz, String arg)
192             throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
193             InvocationTargetException {
194         if (optionalClazz.isPresent()) {
195             return getDefaultInstance(optionalClazz.get(), arg);
196         }
197         return Optional.empty();
198     }
199
200     public static List<Class<?>> getConstructorParameterTypes(Class<?> clazz, Class<?> prefer) {
201
202         Constructor<?>[] constructors = clazz.getConstructors();
203         List<Class<?>> res = new ArrayList<>();
204         for (Constructor<?> c : constructors) {
205             Class<?>[] ptypes = c.getParameterTypes();
206             if (ptypes.length == 1) {
207                 res.add(ptypes[0]);
208             }
209
210             if (prefer != null && ptypes.length == 1 && ptypes[0].equals(prefer)) {
211                 return Arrays.asList(prefer);
212             }
213         }
214         return res;
215     }
216
217     public static boolean implementsInterface(Class<?> clz, Class<?> ifToImplement) {
218         if (clz.equals(ifToImplement)) {
219             return true;
220         }
221         Class<?>[] ifs = clz.getInterfaces();
222         for (Class<?> iff : ifs) {
223             if (iff.equals(ifToImplement)) {
224                 return true;
225             }
226         }
227         return ifToImplement.isAssignableFrom(clz);
228     }
229
230     /**
231      * Provide mapping of string to attribute names, generated by yang-tools. "netconf-id" converted to "_netconfId"
232      *
233      * @param name with attribute name, not null or empty
234      * @return converted string or null if name was empty or null
235      */
236     public @Nullable static String toCamelCaseAttributeName(final String name) {
237         if (name == null || name.isEmpty())
238             return null;
239
240         final StringBuilder ret = new StringBuilder(name.length());
241         if (!name.startsWith("_"))
242             ret.append('_');
243         ret.append(toCamelCase(name));
244         return ret.toString();
245     }
246
247     public static String toCamelCase(final String name) {
248         int start = 0;
249         final StringBuilder ret = new StringBuilder(name.length());
250         for (final String word : name.split("-")) {
251             if (!word.isEmpty()) {
252                 if (start++ == 0) {
253                     ret.append(Character.toLowerCase(word.charAt(0)));
254                 } else {
255                     ret.append(Character.toUpperCase(word.charAt(0)));
256                 }
257                 ret.append(word.substring(1));
258             }
259         }
260         return ret.toString();
261     }
262
263     public static String toCamelCaseClassName(final String name) {
264         final String clsName = toCamelCase(name);
265         return clsName.substring(0, 1).toUpperCase() + clsName.substring(1);
266     }
267
268     private static BundleContext getBundleContext() {
269         Bundle bundle = FrameworkUtil.getBundle(YangToolsMapperHelper.class);
270         return bundle != null ? bundle.getBundleContext() : null;
271     }
272
273     public static boolean hasTime(Notification notification) {
274         return notification instanceof EventInstantAware;
275     }
276
277     public static boolean hasTime(DOMNotification notification) {
278         return notification instanceof DOMEvent;
279     }
280
281     /**
282      * Get time instant from notification if available or default
283      * @param notification
284      * @param defaultValue
285      * @return DateAndTime
286      */
287     public static DateAndTime getTime(Notification notification, Instant defaultValue) {
288         Instant time;
289         if (hasTime(notification)) { // If notification class extends/implements the EventInstantAware
290             time = ((EventInstantAware) notification).eventInstant();
291             LOG.debug("Event time {}", time);
292         } else {
293             time = defaultValue;
294             LOG.debug("Defaulting to actual time of processing the notification - {}", time);
295         }
296         return DateAndTime.getDefaultInstance(ZonedDateTime.ofInstant(time, ZoneOffset.UTC).format(formatterOutput));
297     }
298
299     /**
300      * Get time instant from notification if available or actual time
301      * @param notification
302      * @return DateAndTime
303      */
304     public static DateAndTime getTime(Notification notification) {
305         return getTime(notification, Instant.now());
306     }
307
308     /**
309      * Get time instant from DOM notification if available or default
310      * @param DOM notification
311      * @param defaultValue
312      * @return DateAndTime
313      */
314     public static DateAndTime getTime(DOMNotification notification, Instant defaultValue) {
315         Instant time;
316         if (hasTime(notification)) { // If notification class extends/implements the EventInstantAware
317             time = ((DOMEvent) notification).getEventInstant();
318             LOG.debug("Event time {}", time);
319         } else {
320             time = defaultValue;
321             LOG.debug("Defaulting to actual time of processing the notification - {}", time);
322         }
323         return DateAndTime.getDefaultInstance(ZonedDateTime.ofInstant(time, ZoneOffset.UTC).format(formatterOutput));
324     }
325
326     /**
327      * Get time instant from notification if available or actual time
328      * @param DOM notification
329      * @return DateAndTime
330      */
331     public static DateAndTime getTime(DOMNotification notification) {
332         return getTime(notification, Instant.now());
333     }
334 }