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