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 static java.util.Objects.requireNonNull;
12 import com.google.common.annotations.Beta;
13 import com.google.common.base.Throwables;
14 import java.lang.invoke.MethodHandle;
15 import java.lang.invoke.MethodHandles;
16 import java.lang.invoke.MethodHandles.Lookup;
17 import java.lang.invoke.MethodType;
18 import java.lang.reflect.Constructor;
19 import java.lang.reflect.Field;
20 import java.lang.reflect.InvocationTargetException;
21 import java.security.AccessController;
22 import java.security.PrivilegedAction;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
27 * Utility class for instantiating value-type generated objects with String being the base type. Unlike the normal
28 * constructor, instances of this class bypass string validation.
31 * THE USE OF THIS CLASS IS DANGEROUS AND SHOULD ONLY BE USED TO IMPLEMENT WELL-AUDITED AND CORRECT UTILITY METHODS
32 * SHIPPED WITH MODELS TO PROVIDE INSTANTIATION FROM TYPES DIFFERENT THAN STRING.
35 * APPLICATION CODE <em>MUST NOT</em> USE THIS CLASS DIRECTLY. VIOLATING THIS CONSTRAINT HAS SECURITY AND CORRECTNESS
36 * IMPLICATIONS ON EVERY USER INTERACTING WITH THE RESULTING OBJECTS.
38 * @param <T> Resulting object type
41 public final class StringValueObjectFactory<T> {
42 private static final MethodType CONSTRUCTOR_METHOD_TYPE = MethodType.methodType(Object.class, Object.class);
43 private static final MethodType SETTER_METHOD_TYPE = MethodType.methodType(void.class, Object.class, String.class);
44 private static final Logger LOG = LoggerFactory.getLogger(StringValueObjectFactory.class);
45 private static final Lookup LOOKUP = MethodHandles.lookup();
47 private final MethodHandle constructor;
48 private final MethodHandle setter;
49 private final T template;
51 private StringValueObjectFactory(final T template, final MethodHandle constructor, final MethodHandle setter) {
52 this.template = requireNonNull(template);
53 this.constructor = constructor.bindTo(template);
54 this.setter = requireNonNull(setter);
57 public static <T> StringValueObjectFactory<T> create(final Class<T> clazz, final String templateString) {
58 final Constructor<T> stringConstructor;
60 stringConstructor = clazz.getConstructor(String.class);
61 } catch (NoSuchMethodException e) {
62 throw new IllegalArgumentException(String.format("%s does not have a String constructor", clazz), e);
67 template = stringConstructor.newInstance(templateString);
68 } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
69 throw new IllegalArgumentException(String.format("Failed to instantiate template %s for '%s'", clazz,
73 final Constructor<T> copyConstructor;
75 copyConstructor = clazz.getConstructor(clazz);
76 } catch (NoSuchMethodException e) {
77 throw new IllegalArgumentException(String.format("%s does not have a copy constructor", clazz), e);
80 final Field f = findValueField(clazz);
81 final StringValueObjectFactory<T> ret;
83 ret = new StringValueObjectFactory<>(template,
84 LOOKUP.unreflectConstructor(copyConstructor).asType(CONSTRUCTOR_METHOD_TYPE),
85 LOOKUP.unreflectSetter(f).asType(SETTER_METHOD_TYPE));
86 } catch (IllegalAccessException e) {
87 throw new IllegalStateException("Failed to instantiate method handles", e);
90 // Let us be very defensive and scream loudly if the invocation does not come from the same package. This
91 // is far from perfect, but better than nothing.
92 final Throwable t = new Throwable("Invocation stack");
94 if (matchesPackage(clazz.getPackage().getName(), t.getStackTrace())) {
95 LOG.info("Instantiated factory for {}", clazz);
97 LOG.warn("Instantiated factory for {} outside its package", clazz, t);
103 private static boolean matchesPackage(final String pkg, final StackTraceElement[] stackTrace) {
104 for (StackTraceElement e : stackTrace) {
105 final String sp = e.getClassName();
106 if (sp.startsWith(pkg) && sp.lastIndexOf('.') == pkg.length()) {
114 private static Field findValueField(final Class<?> orig) {
115 NoSuchFieldException cause = null;
116 Class<?> clazz = orig;
117 while (clazz != null) {
120 f = clazz.getDeclaredField("_value");
121 } catch (NoSuchFieldException e) {
123 e.addSuppressed(cause);
126 clazz = clazz.getSuperclass();
130 return AccessController.doPrivileged((PrivilegedAction<Field>) () -> {
131 f.setAccessible(true);
136 throw new IllegalArgumentException(orig + " nor its superclasses define required internal field _value", cause);
139 @SuppressWarnings("checkstyle:illegalCatch")
140 public T newInstance(final String string) {
141 requireNonNull(string, "Argument may not be null");
144 final T ret = (T) constructor.invokeExact();
145 setter.invokeExact(ret, string);
146 LOG.trace("Instantiated new object {} value {}", ret.getClass(), string);
148 } catch (Throwable e) {
149 Throwables.throwIfUnchecked(e);
150 throw new RuntimeException(e);
154 public T getTemplate() {