BUG-2825: introduce Ipv4 prefix parser for short bytes
[mdsal.git] / binding / yang-binding / src / main / java / org / opendaylight / yangtools / yang / binding / util / StringValueObjectFactory.java
1 /*
2  * Copyright (c) 2016 Pantheon Technologies 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.yangtools.yang.binding.util;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Throwables;
13 import java.lang.invoke.MethodHandle;
14 import java.lang.invoke.MethodHandles;
15 import java.lang.invoke.MethodHandles.Lookup;
16 import java.lang.invoke.MethodType;
17 import java.lang.reflect.Constructor;
18 import java.lang.reflect.Field;
19 import java.lang.reflect.InvocationTargetException;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22
23 /**
24  * Utility class for instantiating value-type generated objects with String being the base type. Unlike the normal
25  * constructor, instances of this class bypass string validation.
26  *
27  * THE USE OF THIS CLASS IS DANGEROUS AND SHOULD ONLY BE USED TO IMPLEMENT WELL-AUDITED AND CORRECT UTILITY METHODS
28  * SHIPPED WITH MODELS TO PROVIDE INSTANTIATION FROM TYPES DIFFERENT THAN STRING.
29  *
30  * APPLICATION CODE <em>MUST NOT</em> USE THIS CLASS DIRECTLY. VIOLATING THIS CONSTRAINT HAS SECURITY AND CORRECTNESS
31  * IMPLICATIONS ON EVERY USER INTERACTING WITH THE RESULTING OBJECTS.
32  *
33  * @param <T> Resulting object type
34  */
35 @Beta
36 public final class StringValueObjectFactory<T> {
37     private static final MethodType CONSTRUCTOR_METHOD_TYPE = MethodType.methodType(Object.class, Object.class);
38     private static final MethodType SETTER_METHOD_TYPE = MethodType.methodType(void.class, Object.class, String.class);
39     private static final Logger LOG = LoggerFactory.getLogger(StringValueObjectFactory.class);
40     private static final Lookup LOOKUP = MethodHandles.lookup();
41
42     private final MethodHandle constructor;
43     private final MethodHandle setter;
44     private final T template;
45
46     private StringValueObjectFactory(final T template, final MethodHandle constructor, final MethodHandle setter) {
47         this.template = Preconditions.checkNotNull(template);
48         this.constructor = constructor.bindTo(template);
49         this.setter = Preconditions.checkNotNull(setter);
50     }
51
52     public static <T> StringValueObjectFactory<T> create(final Class<T> clazz, final String templateString) {
53         final Constructor<T> stringConstructor;
54         try {
55             stringConstructor = clazz.getConstructor(String.class);
56         } catch (NoSuchMethodException e) {
57             throw new IllegalArgumentException(String.format("%s does not have a String constructor", clazz), e);
58         }
59
60         final T template;
61         try {
62             template = stringConstructor.newInstance(templateString);
63         } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
64             throw new IllegalArgumentException(String.format("Failed to instantiate template %s for '%s'", clazz,
65                 templateString), e);
66         }
67
68         final Constructor<T> copyConstructor;
69         try {
70             copyConstructor = clazz.getConstructor(clazz);
71         } catch (NoSuchMethodException e) {
72             throw new IllegalArgumentException(String.format("%s does not have a copy constructor", clazz), e);
73         }
74
75         final Field f;
76         try {
77             f = clazz.getDeclaredField("_value");
78         } catch (NoSuchFieldException e) {
79             throw new IllegalArgumentException(String.format("%s does not have required internal field", clazz), e);
80         }
81         f.setAccessible(true);
82
83         final StringValueObjectFactory<T> ret;
84         try {
85             ret = new StringValueObjectFactory<>(template,
86                     LOOKUP.unreflectConstructor(copyConstructor).asType(CONSTRUCTOR_METHOD_TYPE),
87                     LOOKUP.unreflectSetter(f).asType(SETTER_METHOD_TYPE));
88         } catch (IllegalAccessException e) {
89             throw new IllegalStateException("Failed to instantiate method handles", e);
90         }
91
92         // Let us be very defensive and scream loudly if the invocation does not come from the same package. This
93         // is far from perfect, but better than nothing.
94         final Throwable t = new Throwable("Invocation stack");
95         t.fillInStackTrace();
96         if (matchesPackage(clazz.getPackage().getName(), t.getStackTrace())) {
97             LOG.info("Instantiated factory for {}", clazz);
98         } else {
99             LOG.warn("Instantiated factory for {} outside its package", clazz, t);
100         }
101
102         return ret;
103     }
104
105     private static boolean matchesPackage(final String pkg, final StackTraceElement[] stackTrace) {
106         for (StackTraceElement e : stackTrace) {
107             final String sp = e.getClassName();
108             if (sp.startsWith(pkg) && sp.lastIndexOf('.') == pkg.length()) {
109                 return true;
110             }
111         }
112
113         return false;
114     }
115
116     public T newInstance(final String string) {
117         Preconditions.checkNotNull(string, "Argument may not be null");
118
119         try {
120             final T ret = (T) constructor.invokeExact();
121             setter.invokeExact(ret, string);
122             LOG.trace("Instantiated new object {} value {}", ret.getClass(), string);
123             return ret;
124         } catch (Throwable e) {
125             throw Throwables.propagate(e);
126         }
127     }
128
129     public T getTemplate() {
130         return template;
131     }
132 }