2 * Copyright (c) 2016 Pantheon Technologies s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.spec.reflect;
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 java.security.AccessController;
21 import java.security.PrivilegedAction;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
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.
30 * THE USE OF THIS CLASS IS DANGEROUS AND SHOULD ONLY BE USED TO IMPLEMENT WELL-AUDITED AND CORRECT UTILITY METHODS
31 * SHIPPED WITH MODELS TO PROVIDE INSTANTIATION FROM TYPES DIFFERENT THAN STRING.
34 * APPLICATION CODE <em>MUST NOT</em> USE THIS CLASS DIRECTLY. VIOLATING THIS CONSTRAINT HAS SECURITY AND CORRECTNESS
35 * IMPLICATIONS ON EVERY USER INTERACTING WITH THE RESULTING OBJECTS.
37 * @param <T> Resulting object type
40 public final class StringValueObjectFactory<T> {
41 private static final MethodType CONSTRUCTOR_METHOD_TYPE = MethodType.methodType(Object.class, Object.class);
42 private static final MethodType SETTER_METHOD_TYPE = MethodType.methodType(void.class, Object.class, String.class);
43 private static final Logger LOG = LoggerFactory.getLogger(StringValueObjectFactory.class);
44 private static final Lookup LOOKUP = MethodHandles.lookup();
46 private final MethodHandle constructor;
47 private final MethodHandle setter;
48 private final T template;
50 private StringValueObjectFactory(final T template, final MethodHandle constructor, final MethodHandle setter) {
51 this.template = Preconditions.checkNotNull(template);
52 this.constructor = constructor.bindTo(template);
53 this.setter = Preconditions.checkNotNull(setter);
56 public static <T> StringValueObjectFactory<T> create(final Class<T> clazz, final String templateString) {
57 final Constructor<T> stringConstructor;
59 stringConstructor = clazz.getConstructor(String.class);
60 } catch (NoSuchMethodException e) {
61 throw new IllegalArgumentException(String.format("%s does not have a String constructor", clazz), e);
66 template = stringConstructor.newInstance(templateString);
67 } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
68 throw new IllegalArgumentException(String.format("Failed to instantiate template %s for '%s'", clazz,
72 final Constructor<T> copyConstructor;
74 copyConstructor = clazz.getConstructor(clazz);
75 } catch (NoSuchMethodException e) {
76 throw new IllegalArgumentException(String.format("%s does not have a copy constructor", clazz), e);
79 final Field f = findValueField(clazz);
80 final StringValueObjectFactory<T> ret;
82 ret = new StringValueObjectFactory<>(template,
83 LOOKUP.unreflectConstructor(copyConstructor).asType(CONSTRUCTOR_METHOD_TYPE),
84 LOOKUP.unreflectSetter(f).asType(SETTER_METHOD_TYPE));
85 } catch (IllegalAccessException e) {
86 throw new IllegalStateException("Failed to instantiate method handles", e);
89 // Let us be very defensive and scream loudly if the invocation does not come from the same package. This
90 // is far from perfect, but better than nothing.
91 final Throwable t = new Throwable("Invocation stack");
93 if (matchesPackage(clazz.getPackage().getName(), t.getStackTrace())) {
94 LOG.info("Instantiated factory for {}", clazz);
96 LOG.warn("Instantiated factory for {} outside its package", clazz, t);
102 private static boolean matchesPackage(final String pkg, final StackTraceElement[] stackTrace) {
103 for (StackTraceElement e : stackTrace) {
104 final String sp = e.getClassName();
105 if (sp.startsWith(pkg) && sp.lastIndexOf('.') == pkg.length()) {
113 private static Field findValueField(final Class<?> orig) {
114 NoSuchFieldException cause = null;
115 Class<?> clazz = orig;
116 while (clazz != null) {
119 f = clazz.getDeclaredField("_value");
120 } catch (NoSuchFieldException e) {
122 e.addSuppressed(cause);
125 clazz = clazz.getSuperclass();
129 return AccessController.doPrivileged((PrivilegedAction<Field>) () -> {
130 f.setAccessible(true);
135 throw new IllegalArgumentException(orig + " nor its superclasses define required internal field _value", cause);
138 @SuppressWarnings("checkstyle:illegalCatch")
139 public T newInstance(final String string) {
140 Preconditions.checkNotNull(string, "Argument may not be null");
143 final T ret = (T) constructor.invokeExact();
144 setter.invokeExact(ret, string);
145 LOG.trace("Instantiated new object {} value {}", ret.getClass(), string);
147 } catch (Throwable e) {
148 Throwables.throwIfUnchecked(e);
149 throw new RuntimeException(e);
153 public T getTemplate() {