blob: d63cbec5c1317ec6c69bbe8e3cdc9c166c0bafd9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.maven.project.interpolation;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.apache.maven.model.Model;
import org.apache.maven.project.ProjectBuilderConfiguration;
import org.apache.maven.project.path.PathTranslator;
import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
import org.codehaus.plexus.interpolation.Interpolator;
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
import org.codehaus.plexus.interpolation.ValueSource;
import org.codehaus.plexus.logging.Logger;
/**
* StringSearchModelInterpolator
*/
@Deprecated
@Named
@Singleton
public class StringSearchModelInterpolator extends AbstractStringBasedModelInterpolator {
private static final Map<Class<?>, Field[]> FIELDS_BY_CLASS = new WeakHashMap<>();
private static final Map<Class<?>, Boolean> PRIMITIVE_BY_CLASS = new WeakHashMap<>();
public StringSearchModelInterpolator() {}
public StringSearchModelInterpolator(PathTranslator pathTranslator) {
super(pathTranslator);
}
public Model interpolate(Model model, File projectDir, ProjectBuilderConfiguration config, boolean debugEnabled)
throws ModelInterpolationException {
interpolateObject(model, model, projectDir, config, debugEnabled);
return model;
}
protected void interpolateObject(
Object obj, Model model, File projectDir, ProjectBuilderConfiguration config, boolean debugEnabled)
throws ModelInterpolationException {
try {
List<ValueSource> valueSources = createValueSources(model, projectDir, config);
List<InterpolationPostProcessor> postProcessors = createPostProcessors(model, projectDir, config);
InterpolateObjectAction action =
new InterpolateObjectAction(obj, valueSources, postProcessors, debugEnabled, this, getLogger());
ModelInterpolationException error = AccessController.doPrivileged(action);
if (error != null) {
throw error;
}
} finally {
getInterpolator().clearAnswers();
}
}
protected Interpolator createInterpolator() {
StringSearchInterpolator interpolator = new StringSearchInterpolator();
interpolator.setCacheAnswers(true);
return interpolator;
}
private static final class InterpolateObjectAction implements PrivilegedAction<ModelInterpolationException> {
private final boolean debugEnabled;
private final LinkedList<Object> interpolationTargets;
private final StringSearchModelInterpolator modelInterpolator;
private final Logger logger;
private final List<ValueSource> valueSources;
private final List<InterpolationPostProcessor> postProcessors;
InterpolateObjectAction(
Object target,
List<ValueSource> valueSources,
List<InterpolationPostProcessor> postProcessors,
boolean debugEnabled,
StringSearchModelInterpolator modelInterpolator,
Logger logger) {
this.valueSources = valueSources;
this.postProcessors = postProcessors;
this.debugEnabled = debugEnabled;
this.interpolationTargets = new LinkedList<>();
interpolationTargets.add(target);
this.modelInterpolator = modelInterpolator;
this.logger = logger;
}
public ModelInterpolationException run() {
while (!interpolationTargets.isEmpty()) {
Object obj = interpolationTargets.removeFirst();
try {
traverseObjectWithParents(obj.getClass(), obj);
} catch (ModelInterpolationException e) {
return e;
}
}
return null;
}
@SuppressWarnings({"unchecked", "checkstyle:methodlength"})
private void traverseObjectWithParents(Class<?> cls, Object target) throws ModelInterpolationException {
if (cls == null) {
return;
}
if (cls.isArray()) {
evaluateArray(target);
} else if (isQualifiedForInterpolation(cls)) {
Field[] fields = FIELDS_BY_CLASS.computeIfAbsent(cls, k -> cls.getDeclaredFields());
for (Field field : fields) {
Class<?> type = field.getType();
if (isQualifiedForInterpolation(field, type)) {
boolean isAccessible = field.isAccessible();
field.setAccessible(true);
try {
try {
if (String.class == type) {
String value = (String) field.get(target);
if (value != null) {
String interpolated = modelInterpolator.interpolateInternal(
value, valueSources, postProcessors, debugEnabled);
if (!interpolated.equals(value)) {
field.set(target, interpolated);
}
}
} else if (Collection.class.isAssignableFrom(type)) {
Collection<Object> c = (Collection<Object>) field.get(target);
if (c != null && !c.isEmpty()) {
List<Object> originalValues = new ArrayList<>(c);
try {
c.clear();
} catch (UnsupportedOperationException e) {
if (debugEnabled && logger != null) {
logger.debug("Skipping interpolation of field: " + field + " in: "
+ cls.getName()
+ "; it is an unmodifiable collection.");
}
continue;
}
for (Object value : originalValues) {
if (value != null) {
if (String.class == value.getClass()) {
String interpolated = modelInterpolator.interpolateInternal(
(String) value, valueSources, postProcessors, debugEnabled);
if (!interpolated.equals(value)) {
c.add(interpolated);
} else {
c.add(value);
}
} else {
c.add(value);
if (value.getClass().isArray()) {
evaluateArray(value);
} else {
interpolationTargets.add(value);
}
}
} else {
// add the null back in...not sure what else to do...
c.add(value);
}
}
}
} else if (Map.class.isAssignableFrom(type)) {
Map<Object, Object> m = (Map<Object, Object>) field.get(target);
if (m != null && !m.isEmpty()) {
for (Map.Entry<Object, Object> entry : m.entrySet()) {
Object value = entry.getValue();
if (value != null) {
if (String.class == value.getClass()) {
String interpolated = modelInterpolator.interpolateInternal(
(String) value, valueSources, postProcessors, debugEnabled);
if (!interpolated.equals(value)) {
try {
entry.setValue(interpolated);
} catch (UnsupportedOperationException e) {
if (debugEnabled && logger != null) {
logger.debug("Skipping interpolation of field: " + field
+ " (key: " + entry.getKey() + ") in: "
+ cls.getName()
+ "; it is an unmodifiable collection.");
}
}
}
} else {
if (value.getClass().isArray()) {
evaluateArray(value);
} else {
interpolationTargets.add(value);
}
}
}
}
}
} else {
Object value = field.get(target);
if (value != null) {
if (field.getType().isArray()) {
evaluateArray(value);
} else {
interpolationTargets.add(value);
}
}
}
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new ModelInterpolationException(
"Failed to interpolate field: " + field + " on class: " + cls.getName(), e);
}
} finally {
field.setAccessible(isAccessible);
}
}
}
traverseObjectWithParents(cls.getSuperclass(), target);
}
}
private boolean isQualifiedForInterpolation(Class<?> cls) {
return !cls.getPackage().getName().startsWith("java")
&& !cls.getPackage().getName().startsWith("sun.nio.fs");
}
private boolean isQualifiedForInterpolation(Field field, Class<?> fieldType) {
if (!PRIMITIVE_BY_CLASS.containsKey(fieldType)) {
PRIMITIVE_BY_CLASS.put(fieldType, fieldType.isPrimitive());
}
if (PRIMITIVE_BY_CLASS.get(fieldType)) {
return false;
}
// if ( fieldType.isPrimitive() )
// {
// return false;
// }
return !"parent".equals(field.getName());
}
private void evaluateArray(Object target) throws ModelInterpolationException {
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 = modelInterpolator.interpolateInternal(
(String) value, valueSources, postProcessors, debugEnabled);
if (!interpolated.equals(value)) {
Array.set(target, i, interpolated);
}
} else {
interpolationTargets.add(value);
}
}
}
}
}
}