/* * Copyright (c) 2016 Pantheon Technologies s.r.o. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.mdsal.binding.spec.reflect; import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for instantiating value-type generated objects with String being the base type. Unlike the normal * constructor, instances of this class bypass string validation. * *

* THE USE OF THIS CLASS IS DANGEROUS AND SHOULD ONLY BE USED TO IMPLEMENT WELL-AUDITED AND CORRECT UTILITY METHODS * SHIPPED WITH MODELS TO PROVIDE INSTANTIATION FROM TYPES DIFFERENT THAN STRING. * *

* APPLICATION CODE MUST NOT USE THIS CLASS DIRECTLY. VIOLATING THIS CONSTRAINT HAS SECURITY AND CORRECTNESS * IMPLICATIONS ON EVERY USER INTERACTING WITH THE RESULTING OBJECTS. * * @param Resulting object type */ @Beta public final class StringValueObjectFactory { private static final MethodType CONSTRUCTOR_METHOD_TYPE = MethodType.methodType(Object.class, Object.class); private static final MethodType SETTER_METHOD_TYPE = MethodType.methodType(void.class, Object.class, String.class); private static final Logger LOG = LoggerFactory.getLogger(StringValueObjectFactory.class); private static final Lookup LOOKUP = MethodHandles.lookup(); private final MethodHandle constructor; private final MethodHandle setter; private final T template; private StringValueObjectFactory(final T template, final MethodHandle constructor, final MethodHandle setter) { this.template = Preconditions.checkNotNull(template); this.constructor = constructor.bindTo(template); this.setter = Preconditions.checkNotNull(setter); } public static StringValueObjectFactory create(final Class clazz, final String templateString) { final Constructor stringConstructor; try { stringConstructor = clazz.getConstructor(String.class); } catch (NoSuchMethodException e) { throw new IllegalArgumentException(String.format("%s does not have a String constructor", clazz), e); } final T template; try { template = stringConstructor.newInstance(templateString); } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) { throw new IllegalArgumentException(String.format("Failed to instantiate template %s for '%s'", clazz, templateString), e); } final Constructor copyConstructor; try { copyConstructor = clazz.getConstructor(clazz); } catch (NoSuchMethodException e) { throw new IllegalArgumentException(String.format("%s does not have a copy constructor", clazz), e); } final Field f = findValueField(clazz); f.setAccessible(true); final StringValueObjectFactory ret; try { ret = new StringValueObjectFactory<>(template, LOOKUP.unreflectConstructor(copyConstructor).asType(CONSTRUCTOR_METHOD_TYPE), LOOKUP.unreflectSetter(f).asType(SETTER_METHOD_TYPE)); } catch (IllegalAccessException e) { throw new IllegalStateException("Failed to instantiate method handles", e); } // Let us be very defensive and scream loudly if the invocation does not come from the same package. This // is far from perfect, but better than nothing. final Throwable t = new Throwable("Invocation stack"); t.fillInStackTrace(); if (matchesPackage(clazz.getPackage().getName(), t.getStackTrace())) { LOG.info("Instantiated factory for {}", clazz); } else { LOG.warn("Instantiated factory for {} outside its package", clazz, t); } return ret; } private static boolean matchesPackage(final String pkg, final StackTraceElement[] stackTrace) { for (StackTraceElement e : stackTrace) { final String sp = e.getClassName(); if (sp.startsWith(pkg) && sp.lastIndexOf('.') == pkg.length()) { return true; } } return false; } private static Field findValueField(final Class orig) { NoSuchFieldException cause = null; Class clazz = orig; do { try { return clazz.getDeclaredField("_value"); } catch (NoSuchFieldException e) { if (cause != null) { e.addSuppressed(cause); } cause = e; } clazz = clazz.getSuperclass(); } while (clazz != null); throw new IllegalArgumentException(orig + " nor its superclasses define required internal field _value", cause); } @SuppressWarnings("checkstyle:illegalCatch") public T newInstance(final String string) { Preconditions.checkNotNull(string, "Argument may not be null"); try { final T ret = (T) constructor.invokeExact(); setter.invokeExact(ret, string); LOG.trace("Instantiated new object {} value {}", ret.getClass(), string); return ret; } catch (Throwable e) { Throwables.throwIfUnchecked(e); throw new IllegalStateException("Failed to instantiate object with value " + string, e); } } public T getTemplate() { return template; } }