| /* |
| * 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); |
| } |
| } |
| } |
| } |
| } |
| } |