001/**
002 * Copyright 2011-2015 John Ericksen
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *    http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.parceler;
017
018import java.lang.reflect.*;
019import java.security.AccessController;
020import java.security.PrivilegedActionException;
021import java.security.PrivilegedExceptionAction;
022
023/**
024 * Utility class for performing a variety of operations through reflection.  This functionality should be used sparingly
025 * as frequent calls can cause performance issues.
026 *
027 * @author John Ericksen
028 */
029public final class InjectionUtil {
030
031    public static final String GET_FIELD_METHOD = "getField";
032    public static final String SET_FIELD_METHOD = "setField";
033    public static final String CALL_METHOD_METHOD = "callMethod";
034    public static final String CALL_CONSTRUCTOR_METHOD = "callConstructor";
035
036    private InjectionUtil() {
037        //singleton constructor
038    }
039
040    public static final class GenericType<T>{}
041
042    /**
043     * Returns the value of a field.
044     *
045     * @param returnType type of the field
046     * @param targetClass class represented by the target parameter
047     * @param target object containing the field
048     * @param field name of the field
049     * @param <T> type parameter
050     * @return field value
051     */
052    public static <T> T getField(Class<T> returnType, Class<?> targetClass, Object target, String field) {
053        try {
054            Field declaredField = targetClass.getDeclaredField(field);
055
056            return AccessController.doPrivileged(
057                    new GetFieldPrivilegedAction<T>(declaredField, target));
058
059        } catch (NoSuchFieldException e) {
060            throw new ParcelerRuntimeException(
061                    "NoSuchFieldException Exception during field injection: " + field + " in " + target.getClass(), e);
062        } catch (PrivilegedActionException e) {
063            throw new ParcelerRuntimeException("PrivilegedActionException Exception during field injection", e);
064        } catch (Exception e) {
065            throw new ParcelerRuntimeException("Exception during field injection", e);
066        }
067    }
068
069    public static <T> T getField(GenericType<T> returnType, Class<?> targetClass, Object target, String field) {
070        return (T) getField(Object.class, targetClass, target, field);
071    }
072
073    private static final class GetFieldPrivilegedAction<T> extends AccessibleElementPrivilegedAction<T, Field> {
074
075        private final Object target;
076
077        private GetFieldPrivilegedAction(Field classField, Object target) {
078            super(classField);
079            this.target = target;
080        }
081
082        @Override
083        public T run(Field classField) throws IllegalAccessException {
084            return (T) classField.get(target);
085        }
086    }
087
088    /**
089     * Updates field with the given value.
090     *
091     * @param targetClass class representing the object containing the field.
092     * @param target object containing the field to update
093     * @param field name of the field
094     * @param value object to update the field to
095     */
096    public static void setField(Class<?> targetClass, Object target, String field, Object value) {
097        try {
098            Field classField = targetClass.getDeclaredField(field);
099
100            AccessController.doPrivileged(
101                    new SetFieldPrivilegedAction(classField, target, value));
102
103        } catch (NoSuchFieldException e) {
104            throw new ParcelerRuntimeException(
105                    "NoSuchFieldException Exception during field injection: " + field + " in " + target.getClass(), e);
106        } catch (PrivilegedActionException e) {
107            throw new ParcelerRuntimeException("PrivilegedActionException Exception during field injection", e);
108        } catch (Exception e) {
109            throw new ParcelerRuntimeException("Exception during field injection", e);
110        }
111    }
112
113    private static final class SetFieldPrivilegedAction extends AccessibleElementPrivilegedAction<Void, Field> {
114
115        private final Object target;
116        private final Object value;
117
118        private SetFieldPrivilegedAction(Field classField, Object target, Object value) {
119            super(classField);
120            this.target = target;
121            this.value = value;
122        }
123
124        @Override
125        public Void run(Field classField) throws IllegalAccessException {
126            classField.set(target, value);
127
128            return null;
129        }
130    }
131
132    /**
133     * Calls a method with the provided arguments as parameters.
134     *
135     * @param retClass the method return value
136     * @param targetClass the instance class
137     * @param target the instance containing the method
138     * @param method the method name
139     * @param argClasses types of the method arguments
140     * @param args method arguments used during invocation
141     * @param <T> relating type parameter
142     * @return method return value
143     */
144    public static <T> T callMethod(Class<T> retClass, Class<?> targetClass, Object target, String method, Class[] argClasses, Object[] args) {
145        try {
146            Method classMethod = targetClass.getDeclaredMethod(method, argClasses);
147
148            return AccessController.doPrivileged(
149                    new SetMethodPrivilegedAction<T>(classMethod, target, args));
150
151        } catch (NoSuchMethodException e) {
152            throw new ParcelerRuntimeException("Exception during method injection: NoSuchFieldException", e);
153        } catch (PrivilegedActionException e) {
154            throw new ParcelerRuntimeException("PrivilegedActionException Exception during field injection", e);
155        } catch (Exception e) {
156            throw new ParcelerRuntimeException("Exception during field injection", e);
157        }
158    }
159
160    public static <T> T callMethod(GenericType<T> retClass, Class<?> targetClass, Object target, String method, Class[] argClasses, Object[] args) {
161        return (T) callMethod(Object.class, targetClass, target, method, argClasses, args);
162    }
163
164    private static final class SetMethodPrivilegedAction<T> extends AccessibleElementPrivilegedAction<T, Method> {
165
166        private final Object target;
167        private final Object[] args;
168
169        private SetMethodPrivilegedAction(Method classMethod, Object target, Object[] args) {
170            super(classMethod);
171            this.target = target;
172            this.args = args;
173        }
174
175        public T run(Method classMethod) throws InvocationTargetException, IllegalAccessException {
176            return (T) classMethod.invoke(target, args);
177        }
178    }
179
180
181    /**
182     * Instantiates a class by calling the constructor.
183     *
184     * @param targetClass instance type to construct
185     * @param argClasses argument types accepted by the constructor
186     * @param args constructor argument values
187     * @param <T> relating type parameter
188     * @return instance created by constructor
189     */
190    public static <T> T callConstructor(Class<T> targetClass, Class[] argClasses, Object[] args) {
191        T output;
192
193        try {
194            Constructor classConstructor = targetClass.getDeclaredConstructor(argClasses);
195
196            output = AccessController.doPrivileged(
197                    new SetConstructorPrivilegedAction<T>(classConstructor, args));
198
199        } catch (NoSuchMethodException e) {
200            throw new ParcelerRuntimeException("Exception during method injection: NoSuchMethodException", e);
201        } catch (PrivilegedActionException e) {
202            throw new ParcelerRuntimeException("PrivilegedActionException Exception during field injection", e);
203        } catch (Exception e) {
204            throw new ParcelerRuntimeException("Exception during field injection", e);
205        }
206        return output;
207    }
208
209    public static <T> T callConstructor(GenericType<T> targetClass, Class[] argClasses, Object[] args) {
210        return (T) callConstructor(Object.class, argClasses, args);
211    }
212
213    private static final class SetConstructorPrivilegedAction<T> extends AccessibleElementPrivilegedAction<T, Constructor> {
214        private final Object[] args;
215
216        private SetConstructorPrivilegedAction(Constructor classConstructor, Object[] args) {
217            super(classConstructor);
218            this.args = args;
219        }
220
221        @Override
222        public T run(Constructor classConstructor) throws InvocationTargetException, InstantiationException, IllegalAccessException {
223            return (T) classConstructor.newInstance(args);
224        }
225    }
226
227    private static abstract class AccessibleElementPrivilegedAction<T, E extends AccessibleObject> implements PrivilegedExceptionAction<T> {
228
229        private final E accessible;
230
231        protected AccessibleElementPrivilegedAction(E accessible) {
232            this.accessible = accessible;
233        }
234
235        @Override
236        public T run() throws Exception {
237            boolean previous = this.accessible.isAccessible();
238            accessible.setAccessible(true);
239
240            T output = run(accessible);
241
242            accessible.setAccessible(previous);
243
244            return output;
245        }
246
247        /**
248         * Execute a Privileged Action against the given element which has been toggled to be accessible.
249         *
250         * @param element input AccessibleObject
251         * @return T
252         * @throws Exception
253         */
254        public abstract T run(E element) throws Exception;
255    }
256}