All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.plexus.interpolation.object.FieldBasedObjectInterpolator Maven / Gradle / Ivy

There is a newer version: 3.0.0-alpha-3
Show newest version
package org.codehaus.plexus.interpolation.object;

/*
 * Copyright 2001-2008 Codehaus Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import org.codehaus.plexus.interpolation.BasicInterpolator;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.Interpolator;
import org.codehaus.plexus.interpolation.RecursionInterceptor;
import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;

/**
 * Reflectively traverses an object graph and uses an {@link Interpolator} instance to resolve any String fields in the
 * graph.
 * 

* NOTE: This code is based on a reimplementation of ModelInterpolator in * maven-project 2.1.0-M1, which became a performance bottleneck when the * interpolation process became a hotspot.

* * @author jdcasey */ public class FieldBasedObjectInterpolator implements ObjectInterpolator { public static final Set DEFAULT_BLACKLISTED_FIELD_NAMES; public static final Set DEFAULT_BLACKLISTED_PACKAGE_PREFIXES; private static final Map fieldsByClass = new WeakHashMap(); private static final Map fieldIsPrimitiveByClass = new WeakHashMap(); static { Set blacklistedFields = new HashSet(); blacklistedFields.add("parent"); DEFAULT_BLACKLISTED_FIELD_NAMES = Collections.unmodifiableSet(blacklistedFields); Set blacklistedPackages = new HashSet(); blacklistedPackages.add("java"); DEFAULT_BLACKLISTED_PACKAGE_PREFIXES = Collections.unmodifiableSet(blacklistedPackages); } /** * Clear out the Reflection caches kept for the most expensive operations encountered: field lookup and primitive * queries for fields. These caches are static since they apply at the class level, not the instance level. */ public static void clearCaches() { fieldsByClass.clear(); fieldIsPrimitiveByClass.clear(); } private Set blacklistedFieldNames; private Set blacklistedPackagePrefixes; private List warnings = new ArrayList(); /** * Use the default settings for blacklisted fields and packages, where fields named 'parent' and classes in packages * starting with 'java' will not be interpolated. */ public FieldBasedObjectInterpolator() { this.blacklistedFieldNames = DEFAULT_BLACKLISTED_FIELD_NAMES; this.blacklistedPackagePrefixes = DEFAULT_BLACKLISTED_PACKAGE_PREFIXES; } /** * Use the given black-lists to limit the interpolation of fields and classes (by package). * * @param blacklistedFieldNames The list of field names to ignore * @param blacklistedPackagePrefixes The list of package prefixes whose classes should be ignored */ public FieldBasedObjectInterpolator(Set blacklistedFieldNames, Set blacklistedPackagePrefixes) { this.blacklistedFieldNames = blacklistedFieldNames; this.blacklistedPackagePrefixes = blacklistedPackagePrefixes; } /** * Returns true if the last interpolation execution generated warnings. */ public boolean hasWarnings() { return warnings != null && !warnings.isEmpty(); } /** * Retrieve the {@link List} of warnings ({@link ObjectInterpolationWarning} * instances) generated during the last interpolation execution. */ public List getWarnings() { return new ArrayList(warnings); } /** * Using reflective field access and mutation, traverse the object graph from the given starting point and * interpolate any Strings found in that graph using the given {@link Interpolator}. Limits to this process can be * managed using the black lists configured in the constructor. * * @param target The starting point of the object graph to traverse * @param interpolator The {@link Interpolator} used to resolve any Strings encountered during traversal. * NOTE: Uses {@link SimpleRecursionInterceptor}. */ public void interpolate(Object target, BasicInterpolator interpolator) throws InterpolationException { interpolate(target, interpolator, new SimpleRecursionInterceptor()); } /** * Using reflective field access and mutation, traverse the object graph from the given starting point and * interpolate any Strings found in that graph using the given {@link Interpolator}. Limits to this process can be * managed using the black lists configured in the constructor. * * @param target The starting point of the object graph to traverse * @param interpolator The {@link Interpolator} used to resolve any Strings encountered during traversal. * @param recursionInterceptor The {@link RecursionInterceptor} used to detect cyclical expressions in the graph */ public void interpolate(Object target, BasicInterpolator interpolator, RecursionInterceptor recursionInterceptor) throws InterpolationException { warnings.clear(); InterpolateObjectAction action = new InterpolateObjectAction( target, interpolator, recursionInterceptor, blacklistedFieldNames, blacklistedPackagePrefixes, warnings); InterpolationException error = (InterpolationException) AccessController.doPrivileged(action); if (error != null) { throw error; } } private static final class InterpolateObjectAction implements PrivilegedAction { private final LinkedList interpolationTargets; private final BasicInterpolator interpolator; private final Set blacklistedFieldNames; private final String[] blacklistedPackagePrefixes; private final List warningCollector; private final RecursionInterceptor recursionInterceptor; /** * Setup an object graph traversal for the given target starting point. This will initialize a queue of objects * to traverse and interpolate by adding the target object. */ public InterpolateObjectAction( Object target, BasicInterpolator interpolator, RecursionInterceptor recursionInterceptor, Set blacklistedFieldNames, Set blacklistedPackagePrefixes, List warningCollector) { this.recursionInterceptor = recursionInterceptor; this.blacklistedFieldNames = blacklistedFieldNames; this.warningCollector = warningCollector; this.blacklistedPackagePrefixes = (String[]) blacklistedPackagePrefixes.toArray(new String[blacklistedPackagePrefixes.size()]); this.interpolationTargets = new LinkedList(); interpolationTargets.add(new InterpolationTarget(target, "")); this.interpolator = interpolator; } /** * As long as the traversal queue is non-empty, traverse the next object in the queue. If an interpolation error * occurs, return it immediately. */ public Object run() { while (!interpolationTargets.isEmpty()) { InterpolationTarget target = interpolationTargets.removeFirst(); try { traverseObjectWithParents(target.value.getClass(), target); } catch (InterpolationException e) { return e; } } return null; } /** * Traverse the given object, interpolating any String fields and adding non-primitive field values to the * interpolation queue for later processing. */ private void traverseObjectWithParents(Class cls, InterpolationTarget target) throws InterpolationException { Object obj = target.value; String basePath = target.path; if (cls == null) { return; } if (cls.isArray()) { evaluateArray(obj, basePath); } else if (isQualifiedForInterpolation(cls)) { Field[] fields = fieldsByClass.get(cls); if (fields == null) { fields = cls.getDeclaredFields(); fieldsByClass.put(cls, fields); } for (Field field : fields) { Class type = field.getType(); if (isQualifiedForInterpolation(field, type)) { boolean isAccessible = field.isAccessible(); synchronized (cls) { field.setAccessible(true); try { try { if (String.class == type) { interpolateString(obj, field); } else if (Collection.class.isAssignableFrom(type)) { if (interpolateCollection(obj, basePath, field)) { continue; } } else if (Map.class.isAssignableFrom(type)) { interpolateMap(obj, basePath, field); } else { interpolateObject(obj, basePath, field); } } catch (IllegalArgumentException e) { warningCollector.add(new ObjectInterpolationWarning( "Failed to interpolate field. Skipping.", basePath + "." + field.getName(), e)); } catch (IllegalAccessException e) { warningCollector.add(new ObjectInterpolationWarning( "Failed to interpolate field. Skipping.", basePath + "." + field.getName(), e)); } } finally { field.setAccessible(isAccessible); } } } } traverseObjectWithParents(cls.getSuperclass(), target); } } private void interpolateObject(Object obj, String basePath, Field field) throws IllegalAccessException, InterpolationException { Object value = field.get(obj); if (value != null) { if (field.getType().isArray()) { evaluateArray(value, basePath + "." + field.getName()); } else { interpolationTargets.add(new InterpolationTarget(value, basePath + "." + field.getName())); } } } private void interpolateMap(Object obj, String basePath, Field field) throws IllegalAccessException, InterpolationException { Map m = (Map) field.get(obj); if (m != null && !m.isEmpty()) { for (Object o : m.entrySet()) { Map.Entry entry = (Map.Entry) o; Object value = entry.getValue(); if (value != null) { if (String.class == value.getClass()) { String interpolated = interpolator.interpolate((String) value, recursionInterceptor); if (!interpolated.equals(value)) { try { entry.setValue(interpolated); } catch (UnsupportedOperationException e) { warningCollector.add(new ObjectInterpolationWarning( "Field is an unmodifiable collection. Skipping interpolation.", basePath + "." + field.getName(), e)); continue; } } } else { if (value.getClass().isArray()) { evaluateArray(value, basePath + "." + field.getName()); } else { interpolationTargets.add( new InterpolationTarget(value, basePath + "." + field.getName())); } } } } } } private boolean interpolateCollection(Object obj, String basePath, Field field) throws IllegalAccessException, InterpolationException { Collection c = (Collection) field.get(obj); if (c != null && !c.isEmpty()) { List originalValues = new ArrayList(c); try { c.clear(); } catch (UnsupportedOperationException e) { warningCollector.add(new ObjectInterpolationWarning( "Field is an unmodifiable collection. Skipping interpolation.", basePath + "." + field.getName(), e)); return true; } for (Object value : originalValues) { if (value != null) { if (String.class == value.getClass()) { String interpolated = interpolator.interpolate((String) value, recursionInterceptor); if (!interpolated.equals(value)) { c.add(interpolated); } else { c.add(value); } } else { c.add(value); if (value.getClass().isArray()) { evaluateArray(value, basePath + "." + field.getName()); } else { interpolationTargets.add( new InterpolationTarget(value, basePath + "." + field.getName())); } } } else { // add the null back in...not sure what else to do... c.add(value); } } } return false; } private void interpolateString(Object obj, Field field) throws IllegalAccessException, InterpolationException { String value = (String) field.get(obj); if (value != null) { String interpolated = interpolator.interpolate(value, recursionInterceptor); if (!interpolated.equals(value)) { field.set(obj, interpolated); } } } /** * Using the package-prefix blacklist, determine whether the given class is qualified for interpolation, or * whether it should be ignored. */ private boolean isQualifiedForInterpolation(Class cls) { String pkgName = cls.getPackage().getName(); for (String prefix : blacklistedPackagePrefixes) { if (pkgName.startsWith(prefix)) { return false; } } return true; } /** * Using the field-name blacklist and the primitive-field cache, determine whether the given field in the given * class is qualified for interpolation. Primitive fields and fields listed in the blacklist will be ignored. * The primitive-field cache is used to improve the performance of the reflective operations in this method, * since this method is a hotspot. */ private boolean isQualifiedForInterpolation(Field field, Class fieldType) { if (!fieldIsPrimitiveByClass.containsKey(fieldType)) { fieldIsPrimitiveByClass.put(fieldType, fieldType.isPrimitive()); } //noinspection UnnecessaryUnboxing if (fieldIsPrimitiveByClass.get(fieldType)) { return false; } return !blacklistedFieldNames.contains(field.getName()); } /** * Traverse the elements of an array, and interpolate any qualified objects or add them to the traversal queue. */ private void evaluateArray(Object target, String basePath) throws InterpolationException { int len = Array.getLength(target); for (int i = 0; i < len; i++) { Object value = Array.get(target, i); if (value != null) { if (String.class == value.getClass()) { String interpolated = interpolator.interpolate((String) value, recursionInterceptor); if (!interpolated.equals(value)) { Array.set(target, i, interpolated); } } else { interpolationTargets.add(new InterpolationTarget(value, basePath + "[" + i + "]")); } } } } } private static final class InterpolationTarget { private Object value; private String path; private InterpolationTarget(Object value, String path) { this.value = value; this.path = path; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy