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