mirror of https://github.com/apache/maven.git
[MNG-8281] Interpolator service
This commit is contained in:
parent
5c981cdef0
commit
f6417e4944
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* 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.api.services;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.maven.api.Service;
|
||||
import org.apache.maven.api.annotations.Experimental;
|
||||
import org.apache.maven.api.annotations.Nonnull;
|
||||
import org.apache.maven.api.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The Interpolator service provides methods for variable substitution in strings and maps.
|
||||
* It allows for the replacement of placeholders (e.g., ${variable}) with their corresponding values.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@Experimental
|
||||
public interface Interpolator extends Service {
|
||||
|
||||
/**
|
||||
* Interpolates the values in the given map using the provided callback function.
|
||||
* This method defaults to setting empty strings for unresolved placeholders.
|
||||
*
|
||||
* @param properties The map containing key-value pairs to be interpolated.
|
||||
* @param callback The function to resolve variable values not found in the map.
|
||||
*/
|
||||
default void interpolate(@Nonnull Map<String, String> properties, @Nullable Function<String, String> callback) {
|
||||
interpolate(properties, callback, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates the values in the given map using the provided callback function.
|
||||
*
|
||||
* @param map The map containing key-value pairs to be interpolated.
|
||||
* @param callback The function to resolve variable values not found in the map.
|
||||
* @param defaultsToEmpty If true, unresolved placeholders are replaced with empty strings. If false, they are left unchanged.
|
||||
*/
|
||||
default void interpolate(
|
||||
@Nonnull Map<String, String> map, @Nullable Function<String, String> callback, boolean defaultsToEmpty) {
|
||||
interpolate(map, callback, null, defaultsToEmpty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates the values in the given map using the provided callback function.
|
||||
*
|
||||
* @param map The map containing key-value pairs to be interpolated.
|
||||
* @param callback The function to resolve variable values not found in the map.
|
||||
* @param defaultsToEmpty If true, unresolved placeholders are replaced with empty strings. If false, they are left unchanged.
|
||||
*/
|
||||
void interpolate(
|
||||
@Nonnull Map<String, String> map,
|
||||
@Nullable Function<String, String> callback,
|
||||
@Nullable BiFunction<String, String, String> postprocessor,
|
||||
boolean defaultsToEmpty);
|
||||
|
||||
/**
|
||||
* Interpolates a single string value using the provided callback function.
|
||||
* This method defaults to not replacing unresolved placeholders.
|
||||
*
|
||||
* @param val The string to be interpolated.
|
||||
* @param callback The function to resolve variable values.
|
||||
* @return The interpolated string, or null if the input was null.
|
||||
*/
|
||||
@Nullable
|
||||
default String interpolate(@Nullable String val, @Nullable Function<String, String> callback) {
|
||||
return interpolate(val, callback, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates a single string value using the provided callback function.
|
||||
*
|
||||
* @param val The string to be interpolated.
|
||||
* @param callback The function to resolve variable values.
|
||||
* @param defaultsToEmpty If true, unresolved placeholders are replaced with empty strings.
|
||||
* @return The interpolated string, or null if the input was null.
|
||||
*/
|
||||
@Nullable
|
||||
default String interpolate(
|
||||
@Nullable String val, @Nullable Function<String, String> callback, boolean defaultsToEmpty) {
|
||||
return interpolate(val, callback, null, defaultsToEmpty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates a single string value using the provided callback function.
|
||||
*
|
||||
* @param val The string to be interpolated.
|
||||
* @param callback The function to resolve variable values.
|
||||
* @param defaultsToEmpty If true, unresolved placeholders are replaced with empty strings.
|
||||
* @return The interpolated string, or null if the input was null.
|
||||
*/
|
||||
@Nullable
|
||||
String interpolate(
|
||||
@Nullable String val,
|
||||
@Nullable Function<String, String> callback,
|
||||
@Nullable BiFunction<String, String, String> postprocessor,
|
||||
boolean defaultsToEmpty);
|
||||
|
||||
/**
|
||||
* Creates a composite function from a collection of functions.
|
||||
*
|
||||
* @param functions A collection of functions, each taking a String as input and returning a String.
|
||||
* @return A function that applies each function in the collection in order until a non-null result is found.
|
||||
* If all functions return null, the composite function returns null.
|
||||
*
|
||||
* @throws NullPointerException if the input collection is null or contains null elements.
|
||||
*/
|
||||
static Function<String, String> chain(Collection<? extends Function<String, String>> functions) {
|
||||
return s -> {
|
||||
for (Function<String, String> function : functions) {
|
||||
String v = function.apply(s);
|
||||
if (v != null) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Memoizes a given function that takes a String input and produces a String output.
|
||||
* This method creates a new function that caches the results of the original function,
|
||||
* improving performance for repeated calls with the same input.
|
||||
*
|
||||
* @param callback The original function to be memoized. It takes a String as input and returns a String.
|
||||
* @return A new {@code Function<String, String>} that caches the results of the original function.
|
||||
* If the original function returns null for a given input, null will be cached and returned for subsequent calls with the same input.
|
||||
*
|
||||
* @see Function
|
||||
* @see Optional
|
||||
* @see HashMap#computeIfAbsent(Object, Function)
|
||||
*/
|
||||
static Function<String, String> memoize(Function<String, String> callback) {
|
||||
Map<String, Optional<String>> cache = new HashMap<>();
|
||||
return s -> cache.computeIfAbsent(s, v -> Optional.ofNullable(callback.apply(v)))
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.api.services;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import org.apache.maven.api.annotations.Experimental;
|
||||
|
||||
/**
|
||||
* Exception thrown by {@link Interpolator} implementations when an error occurs during interpolation.
|
||||
* This can include syntax errors in variable placeholders or recursive variable references.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@Experimental
|
||||
public class InterpolatorException extends MavenException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -1219149033636851813L;
|
||||
|
||||
/**
|
||||
* Constructs a new InterpolatorException with {@code null} as its
|
||||
* detail message. The cause is not initialized, and may subsequently be
|
||||
* initialized by a call to {@link #initCause}.
|
||||
*/
|
||||
public InterpolatorException() {}
|
||||
|
||||
/**
|
||||
* Constructs a new InterpolatorException with the specified detail message.
|
||||
* The cause is not initialized, and may subsequently be initialized by
|
||||
* a call to {@link #initCause}.
|
||||
*
|
||||
* @param message the detail message. The detail message is saved for
|
||||
* later retrieval by the {@link #getMessage()} method.
|
||||
*/
|
||||
public InterpolatorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new InterpolatorException with the specified detail message and cause.
|
||||
*
|
||||
* <p>Note that the detail message associated with {@code cause} is <i>not</i>
|
||||
* automatically incorporated in this exception's detail message.</p>
|
||||
*
|
||||
* @param message the detail message (which is saved for later retrieval
|
||||
* by the {@link #getMessage()} method).
|
||||
* @param cause the cause (which is saved for later retrieval by the
|
||||
* {@link #getCause()} method). A {@code null} value is
|
||||
* permitted, and indicates that the cause is nonexistent or unknown.
|
||||
*/
|
||||
public InterpolatorException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -91,10 +91,6 @@ under the License.
|
|||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-xml-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.plexus</groupId>
|
||||
<artifactId>plexus-interpolation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.woodstox</groupId>
|
||||
<artifactId>woodstox-core</artifactId>
|
||||
|
|
|
@ -20,6 +20,8 @@ package org.apache.maven.api.services.model;
|
|||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.apache.maven.api.annotations.Nonnull;
|
||||
import org.apache.maven.api.annotations.Nullable;
|
||||
import org.apache.maven.api.model.Model;
|
||||
import org.apache.maven.api.services.ModelBuilderRequest;
|
||||
import org.apache.maven.api.services.ModelProblemCollector;
|
||||
|
@ -32,9 +34,7 @@ import org.apache.maven.api.services.ModelProblemCollector;
|
|||
public interface ModelInterpolator {
|
||||
|
||||
/**
|
||||
* Interpolates expressions in the specified model. Note that implementations are free to either interpolate the
|
||||
* provided model directly or to create a clone of the model and interpolate the clone. Callers should always use
|
||||
* the returned model and must not rely on the input model being updated.
|
||||
* Interpolates expressions in the specified model.
|
||||
*
|
||||
* @param model The model to interpolate, must not be {@code null}.
|
||||
* @param projectDir The project directory, may be {@code null} if the model does not belong to a local project but
|
||||
|
@ -44,5 +44,10 @@ public interface ModelInterpolator {
|
|||
* @return The interpolated model, never {@code null}.
|
||||
* @since 4.0.0
|
||||
*/
|
||||
Model interpolateModel(Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems);
|
||||
@Nonnull
|
||||
Model interpolateModel(
|
||||
@Nonnull Model model,
|
||||
@Nullable Path projectDir,
|
||||
@Nonnull ModelBuilderRequest request,
|
||||
@Nonnull ModelProblemCollector problems);
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public interface RootLocator extends Service {
|
|||
+ " attribute on the root project's model to identify it.";
|
||||
|
||||
@Nonnull
|
||||
default Path findMandatoryRoot(Path basedir) {
|
||||
default Path findMandatoryRoot(@Nullable Path basedir) {
|
||||
Path rootDirectory = findRoot(basedir);
|
||||
if (rootDirectory == null) {
|
||||
throw new IllegalStateException(getNoRootMessage());
|
||||
|
@ -51,7 +51,7 @@ public interface RootLocator extends Service {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
default Path findRoot(Path basedir) {
|
||||
default Path findRoot(@Nullable Path basedir) {
|
||||
Path rootDirectory = basedir;
|
||||
while (rootDirectory != null && !isRootDirectory(rootDirectory)) {
|
||||
rootDirectory = rootDirectory.getParent();
|
||||
|
|
|
@ -28,9 +28,13 @@ import java.nio.file.Path;
|
|||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.maven.api.di.Inject;
|
||||
import org.apache.maven.api.di.Named;
|
||||
import org.apache.maven.api.services.BuilderProblem;
|
||||
import org.apache.maven.api.services.Interpolator;
|
||||
import org.apache.maven.api.services.SettingsBuilder;
|
||||
import org.apache.maven.api.services.SettingsBuilderException;
|
||||
import org.apache.maven.api.services.SettingsBuilderRequest;
|
||||
|
@ -44,12 +48,9 @@ import org.apache.maven.api.settings.Repository;
|
|||
import org.apache.maven.api.settings.RepositoryPolicy;
|
||||
import org.apache.maven.api.settings.Server;
|
||||
import org.apache.maven.api.settings.Settings;
|
||||
import org.apache.maven.internal.impl.model.DefaultInterpolator;
|
||||
import org.apache.maven.settings.v4.SettingsMerger;
|
||||
import org.apache.maven.settings.v4.SettingsTransformer;
|
||||
import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
|
||||
import org.codehaus.plexus.interpolation.InterpolationException;
|
||||
import org.codehaus.plexus.interpolation.MapBasedValueSource;
|
||||
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
|
||||
|
||||
/**
|
||||
* Builds the effective settings from a user settings file and/or a global settings file.
|
||||
|
@ -62,6 +63,17 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
|
|||
|
||||
private final SettingsMerger settingsMerger = new SettingsMerger();
|
||||
|
||||
private final Interpolator interpolator;
|
||||
|
||||
public DefaultSettingsBuilder() {
|
||||
this(new DefaultInterpolator());
|
||||
}
|
||||
|
||||
@Inject
|
||||
public DefaultSettingsBuilder(Interpolator interpolator) {
|
||||
this.interpolator = interpolator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SettingsBuilderResult build(SettingsBuilderRequest request) throws SettingsBuilderException {
|
||||
List<BuilderProblem> problems = new ArrayList<>();
|
||||
|
@ -213,39 +225,10 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
|
|||
}
|
||||
|
||||
private Settings interpolate(Settings settings, SettingsBuilderRequest request, List<BuilderProblem> problems) {
|
||||
|
||||
RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
|
||||
|
||||
interpolator.addValueSource(new MapBasedValueSource(request.getSession().getUserProperties()));
|
||||
|
||||
interpolator.addValueSource(new MapBasedValueSource(request.getSession().getSystemProperties()));
|
||||
|
||||
try {
|
||||
interpolator.addValueSource(new EnvarBasedValueSource());
|
||||
} catch (IOException e) {
|
||||
problems.add(new DefaultBuilderProblem(
|
||||
null,
|
||||
-1,
|
||||
-1,
|
||||
e,
|
||||
"Failed to use environment variables for interpolation: " + e.getMessage(),
|
||||
BuilderProblem.Severity.WARNING));
|
||||
}
|
||||
|
||||
return new SettingsTransformer(value -> {
|
||||
try {
|
||||
return value != null ? interpolator.interpolate(value) : null;
|
||||
} catch (InterpolationException e) {
|
||||
problems.add(new DefaultBuilderProblem(
|
||||
null,
|
||||
-1,
|
||||
-1,
|
||||
e,
|
||||
"Failed to interpolate settings: " + e.getMessage(),
|
||||
BuilderProblem.Severity.WARNING));
|
||||
return value;
|
||||
}
|
||||
})
|
||||
Map<String, String> userProperties = request.getSession().getUserProperties();
|
||||
Map<String, String> systemProperties = request.getSession().getSystemProperties();
|
||||
Function<String, String> src = Interpolator.chain(List.of(userProperties::get, systemProperties::get));
|
||||
return new SettingsTransformer(value -> value != null ? interpolator.interpolate(value, src) : null)
|
||||
.visit(settings);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,9 +25,13 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.maven.api.di.Inject;
|
||||
import org.apache.maven.api.di.Named;
|
||||
import org.apache.maven.api.services.BuilderProblem;
|
||||
import org.apache.maven.api.services.Interpolator;
|
||||
import org.apache.maven.api.services.Source;
|
||||
import org.apache.maven.api.services.ToolchainsBuilder;
|
||||
import org.apache.maven.api.services.ToolchainsBuilderException;
|
||||
|
@ -37,12 +41,9 @@ import org.apache.maven.api.services.xml.ToolchainsXmlFactory;
|
|||
import org.apache.maven.api.services.xml.XmlReaderException;
|
||||
import org.apache.maven.api.services.xml.XmlReaderRequest;
|
||||
import org.apache.maven.api.toolchain.PersistedToolchains;
|
||||
import org.apache.maven.internal.impl.model.DefaultInterpolator;
|
||||
import org.apache.maven.toolchain.v4.MavenToolchainsMerger;
|
||||
import org.apache.maven.toolchain.v4.MavenToolchainsTransformer;
|
||||
import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
|
||||
import org.codehaus.plexus.interpolation.InterpolationException;
|
||||
import org.codehaus.plexus.interpolation.MapBasedValueSource;
|
||||
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
|
||||
|
||||
/**
|
||||
* Builds the effective toolchains from a user toolchains file and/or a global toolchains file.
|
||||
|
@ -53,6 +54,17 @@ public class DefaultToolchainsBuilder implements ToolchainsBuilder {
|
|||
|
||||
private final MavenToolchainsMerger toolchainsMerger = new MavenToolchainsMerger();
|
||||
|
||||
private final Interpolator interpolator;
|
||||
|
||||
public DefaultToolchainsBuilder() {
|
||||
this(new DefaultInterpolator());
|
||||
}
|
||||
|
||||
@Inject
|
||||
public DefaultToolchainsBuilder(Interpolator interpolator) {
|
||||
this.interpolator = interpolator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToolchainsBuilderResult build(ToolchainsBuilderRequest request) throws ToolchainsBuilderException {
|
||||
List<BuilderProblem> problems = new ArrayList<>();
|
||||
|
@ -154,39 +166,10 @@ public class DefaultToolchainsBuilder implements ToolchainsBuilder {
|
|||
|
||||
private PersistedToolchains interpolate(
|
||||
PersistedToolchains toolchains, ToolchainsBuilderRequest request, List<BuilderProblem> problems) {
|
||||
|
||||
RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
|
||||
|
||||
interpolator.addValueSource(new MapBasedValueSource(request.getSession().getUserProperties()));
|
||||
|
||||
interpolator.addValueSource(new MapBasedValueSource(request.getSession().getSystemProperties()));
|
||||
|
||||
try {
|
||||
interpolator.addValueSource(new EnvarBasedValueSource());
|
||||
} catch (IOException e) {
|
||||
problems.add(new DefaultBuilderProblem(
|
||||
null,
|
||||
-1,
|
||||
-1,
|
||||
e,
|
||||
"Failed to use environment variables for interpolation: " + e.getMessage(),
|
||||
BuilderProblem.Severity.WARNING));
|
||||
}
|
||||
|
||||
return new MavenToolchainsTransformer(value -> {
|
||||
try {
|
||||
return value != null ? interpolator.interpolate(value) : null;
|
||||
} catch (InterpolationException e) {
|
||||
problems.add(new DefaultBuilderProblem(
|
||||
null,
|
||||
-1,
|
||||
-1,
|
||||
e,
|
||||
"Failed to interpolate toolchains: " + e.getMessage(),
|
||||
BuilderProblem.Severity.WARNING));
|
||||
return value;
|
||||
}
|
||||
})
|
||||
Map<String, String> userProperties = request.getSession().getUserProperties();
|
||||
Map<String, String> systemProperties = request.getSession().getSystemProperties();
|
||||
Function<String, String> src = Interpolator.chain(List.of(userProperties::get, systemProperties::get));
|
||||
return new MavenToolchainsTransformer(value -> value != null ? interpolator.interpolate(value, src) : null)
|
||||
.visit(toolchains);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,29 +16,84 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.maven.cli.props;
|
||||
package org.apache.maven.internal.impl.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class InterpolationHelper {
|
||||
import org.apache.maven.api.annotations.Nullable;
|
||||
import org.apache.maven.api.di.Named;
|
||||
import org.apache.maven.api.di.Singleton;
|
||||
import org.apache.maven.api.services.Interpolator;
|
||||
import org.apache.maven.api.services.InterpolatorException;
|
||||
|
||||
private InterpolationHelper() {}
|
||||
@Named
|
||||
@Singleton
|
||||
public class DefaultInterpolator implements Interpolator {
|
||||
|
||||
private static final char ESCAPE_CHAR = '\\';
|
||||
private static final String DELIM_START = "${";
|
||||
private static final String DELIM_STOP = "}";
|
||||
private static final String MARKER = "$__";
|
||||
|
||||
@Override
|
||||
public void interpolate(
|
||||
Map<String, String> map,
|
||||
Function<String, String> callback,
|
||||
BiFunction<String, String, String> postprocessor,
|
||||
boolean defaultsToEmpty) {
|
||||
Map<String, String> org = new HashMap<>(map);
|
||||
for (String name : map.keySet()) {
|
||||
map.compute(
|
||||
name,
|
||||
(k, value) -> interpolate(
|
||||
value,
|
||||
name,
|
||||
null,
|
||||
v -> {
|
||||
String r = org.get(v);
|
||||
if (r == null && callback != null) {
|
||||
r = callback.apply(v);
|
||||
}
|
||||
return r;
|
||||
},
|
||||
postprocessor,
|
||||
defaultsToEmpty));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String interpolate(
|
||||
String val,
|
||||
Function<String, String> callback,
|
||||
BiFunction<String, String, String> postprocessor,
|
||||
boolean defaultsToEmpty) {
|
||||
return interpolate(val, null, null, callback, postprocessor, defaultsToEmpty);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String interpolate(
|
||||
@Nullable String val,
|
||||
@Nullable String currentKey,
|
||||
@Nullable Set<String> cycleMap,
|
||||
@Nullable Function<String, String> callback,
|
||||
@Nullable BiFunction<String, String, String> postprocessor,
|
||||
boolean defaultsToEmpty) {
|
||||
return substVars(val, currentKey, cycleMap, null, callback, postprocessor, defaultsToEmpty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform substitution on a property set
|
||||
*
|
||||
* @param properties the property set to perform substitution on
|
||||
* @param callback Callback for substitution
|
||||
*/
|
||||
public static void performSubstitution(Map<String, String> properties, Function<String, String> callback) {
|
||||
performSubstitution(properties, callback, true, true);
|
||||
public void performSubstitution(Map<String, String> properties, Function<String, String> callback) {
|
||||
performSubstitution(properties, callback, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,20 +101,14 @@ public class InterpolationHelper {
|
|||
*
|
||||
* @param properties the property set to perform substitution on
|
||||
* @param callback the callback to obtain substitution values
|
||||
* @param substituteFromConfig If substitute from configuration
|
||||
* @param defaultsToEmptyString sets an empty string if a replacement value is not found, leaves intact otherwise
|
||||
*/
|
||||
public static void performSubstitution(
|
||||
Map<String, String> properties,
|
||||
Function<String, String> callback,
|
||||
boolean substituteFromConfig,
|
||||
boolean defaultsToEmptyString) {
|
||||
public void performSubstitution(
|
||||
Map<String, String> properties, Function<String, String> callback, boolean defaultsToEmptyString) {
|
||||
Map<String, String> org = new HashMap<>(properties);
|
||||
for (String name : properties.keySet()) {
|
||||
properties.compute(
|
||||
name,
|
||||
(k, value) ->
|
||||
substVars(value, name, null, org, callback, substituteFromConfig, defaultsToEmptyString));
|
||||
name, (k, value) -> substVars(value, name, null, org, callback, null, defaultsToEmptyString));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,11 +131,10 @@ public class InterpolationHelper {
|
|||
* @param cycleMap Map of variable references used to detect nested cycles.
|
||||
* @param configProps Set of configuration properties.
|
||||
* @return The value of the specified string after system property substitution.
|
||||
* @throws IllegalArgumentException If there was a syntax error in the
|
||||
* @throws InterpolatorException If there was a syntax error in the
|
||||
* property placeholder syntax or a recursive variable reference.
|
||||
**/
|
||||
public static String substVars(
|
||||
String val, String currentKey, Map<String, String> cycleMap, Map<String, String> configProps) {
|
||||
public String substVars(String val, String currentKey, Set<String> cycleMap, Map<String, String> configProps) {
|
||||
return substVars(val, currentKey, cycleMap, configProps, null);
|
||||
}
|
||||
|
||||
|
@ -110,16 +158,16 @@ public class InterpolationHelper {
|
|||
* @param configProps Set of configuration properties.
|
||||
* @param callback the callback to obtain substitution values
|
||||
* @return The value of the specified string after system property substitution.
|
||||
* @throws IllegalArgumentException If there was a syntax error in the
|
||||
* @throws InterpolatorException If there was a syntax error in the
|
||||
* property placeholder syntax or a recursive variable reference.
|
||||
**/
|
||||
public static String substVars(
|
||||
public String substVars(
|
||||
String val,
|
||||
String currentKey,
|
||||
Map<String, String> cycleMap,
|
||||
Set<String> cycleMap,
|
||||
Map<String, String> configProps,
|
||||
Function<String, String> callback) {
|
||||
return substVars(val, currentKey, cycleMap, configProps, callback, true, false);
|
||||
return substVars(val, currentKey, cycleMap, configProps, callback, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,7 +189,6 @@ public class InterpolationHelper {
|
|||
* @param cycleMap Map of variable references used to detect nested cycles.
|
||||
* @param configProps Set of configuration properties.
|
||||
* @param callback the callback to obtain substitution values
|
||||
* @param substituteFromConfig If substitute from configuration
|
||||
* @param defaultsToEmptyString sets an empty string if a replacement value is not found, leaves intact otherwise
|
||||
* @return The value of the specified string after system property substitution.
|
||||
* @throws IllegalArgumentException If there was a syntax error in the
|
||||
|
@ -150,29 +197,34 @@ public class InterpolationHelper {
|
|||
public static String substVars(
|
||||
String val,
|
||||
String currentKey,
|
||||
Map<String, String> cycleMap,
|
||||
Set<String> cycleMap,
|
||||
Map<String, String> configProps,
|
||||
Function<String, String> callback,
|
||||
boolean substituteFromConfig,
|
||||
BiFunction<String, String, String> postprocessor,
|
||||
boolean defaultsToEmptyString) {
|
||||
return unescape(doSubstVars(
|
||||
val, currentKey, cycleMap, configProps, callback, substituteFromConfig, defaultsToEmptyString));
|
||||
return unescape(
|
||||
doSubstVars(val, currentKey, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString));
|
||||
}
|
||||
|
||||
private static String doSubstVars(
|
||||
String val,
|
||||
String currentKey,
|
||||
Map<String, String> cycleMap,
|
||||
Set<String> cycleMap,
|
||||
Map<String, String> configProps,
|
||||
Function<String, String> callback,
|
||||
boolean substituteFromConfig,
|
||||
BiFunction<String, String, String> postprocessor,
|
||||
boolean defaultsToEmptyString) {
|
||||
if (val == null || val.isEmpty()) {
|
||||
return val;
|
||||
}
|
||||
if (cycleMap == null) {
|
||||
cycleMap = new HashMap<>();
|
||||
cycleMap = new HashSet<>();
|
||||
}
|
||||
|
||||
// Put the current key in the cycle map.
|
||||
cycleMap.put(currentKey, currentKey);
|
||||
if (currentKey != null) {
|
||||
cycleMap.add(currentKey);
|
||||
}
|
||||
|
||||
// Assume we have a value that is something like:
|
||||
// "leading ${foo.${bar}} middle ${baz} trailing"
|
||||
|
@ -219,7 +271,7 @@ public class InterpolationHelper {
|
|||
// Strip expansion modifiers
|
||||
int idx1 = variable.lastIndexOf(":-");
|
||||
int idx2 = variable.lastIndexOf(":+");
|
||||
int idx = idx1 >= 0 && idx2 >= 0 ? Math.min(idx1, idx2) : idx1 >= 0 ? idx1 : idx2;
|
||||
int idx = idx1 >= 0 ? idx2 >= 0 ? Math.min(idx1, idx2) : idx1 : idx2;
|
||||
String op = null;
|
||||
if (idx >= 0) {
|
||||
op = variable.substring(idx);
|
||||
|
@ -227,20 +279,23 @@ public class InterpolationHelper {
|
|||
}
|
||||
|
||||
// Verify that this is not a recursive variable reference.
|
||||
if (cycleMap.get(variable) != null) {
|
||||
throw new IllegalArgumentException("recursive variable reference: " + variable);
|
||||
if (!cycleMap.add(variable)) {
|
||||
throw new InterpolatorException("recursive variable reference: " + variable);
|
||||
}
|
||||
|
||||
String substValue = null;
|
||||
// Get the value of the deepest nested variable placeholder.
|
||||
// Try to configuration properties first.
|
||||
if (substituteFromConfig && configProps != null) {
|
||||
if (configProps != null) {
|
||||
substValue = configProps.get(variable);
|
||||
}
|
||||
if (substValue == null) {
|
||||
if (!variable.isEmpty()) {
|
||||
if (callback != null) {
|
||||
substValue = callback.apply(variable);
|
||||
String s1 = callback.apply(variable);
|
||||
String s2 = doSubstVars(
|
||||
s1, variable, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
|
||||
substValue = postprocessor != null ? postprocessor.apply(variable, s2) : s2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -255,7 +310,7 @@ public class InterpolationHelper {
|
|||
substValue = op.substring(":+".length());
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Bad substitution: ${" + org + "}");
|
||||
throw new InterpolatorException("Bad substitution: ${" + org + "}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,7 +319,7 @@ public class InterpolationHelper {
|
|||
substValue = "";
|
||||
} else {
|
||||
// alters the original token to avoid infinite recursion
|
||||
// altered tokens are reverted in substVarsPreserveUnresolved()
|
||||
// altered tokens are reverted in unescape()
|
||||
substValue = MARKER + "{" + variable + "}";
|
||||
}
|
||||
}
|
||||
|
@ -281,8 +336,7 @@ public class InterpolationHelper {
|
|||
|
||||
// Now perform substitution again, since there could still
|
||||
// be substitutions to make.
|
||||
val = doSubstVars(
|
||||
val, currentKey, cycleMap, configProps, callback, substituteFromConfig, defaultsToEmptyString);
|
||||
val = doSubstVars(val, currentKey, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
|
||||
|
||||
cycleMap.remove(currentKey);
|
||||
|
||||
|
@ -290,12 +344,32 @@ public class InterpolationHelper {
|
|||
return val;
|
||||
}
|
||||
|
||||
public static String escape(String val) {
|
||||
/**
|
||||
* Escapes special characters in the given string to prevent unwanted interpolation.
|
||||
*
|
||||
* @param val The string to be escaped.
|
||||
* @return The escaped string.
|
||||
*/
|
||||
@Nullable
|
||||
public static String escape(@Nullable String val) {
|
||||
if (val == null || val.isEmpty()) {
|
||||
return val;
|
||||
}
|
||||
return val.replace("$", MARKER);
|
||||
}
|
||||
|
||||
private static String unescape(String val) {
|
||||
val = val.replaceAll("\\" + MARKER, "\\$");
|
||||
/**
|
||||
* Unescapes previously escaped characters in the given string.
|
||||
*
|
||||
* @param val The string to be unescaped.
|
||||
* @return The unescaped string.
|
||||
*/
|
||||
@Nullable
|
||||
public static String unescape(@Nullable String val) {
|
||||
if (val == null || val.isEmpty()) {
|
||||
return val;
|
||||
}
|
||||
val = val.replace(MARKER, "$");
|
||||
int escape = val.indexOf(ESCAPE_CHAR);
|
||||
while (escape >= 0 && escape < val.length() - 1) {
|
||||
char c = val.charAt(escape + 1);
|
|
@ -42,6 +42,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.regex.Matcher;
|
||||
|
@ -71,6 +72,8 @@ import org.apache.maven.api.model.Profile;
|
|||
import org.apache.maven.api.model.Repository;
|
||||
import org.apache.maven.api.services.BuilderProblem;
|
||||
import org.apache.maven.api.services.BuilderProblem.Severity;
|
||||
import org.apache.maven.api.services.Interpolator;
|
||||
import org.apache.maven.api.services.InterpolatorException;
|
||||
import org.apache.maven.api.services.MavenException;
|
||||
import org.apache.maven.api.services.ModelBuilder;
|
||||
import org.apache.maven.api.services.ModelBuilderException;
|
||||
|
@ -110,11 +113,6 @@ import org.apache.maven.api.spi.ModelParserException;
|
|||
import org.apache.maven.api.spi.ModelTransformer;
|
||||
import org.apache.maven.internal.impl.util.PhasingExecutor;
|
||||
import org.apache.maven.model.v4.MavenTransformer;
|
||||
import org.codehaus.plexus.interpolation.InterpolationException;
|
||||
import org.codehaus.plexus.interpolation.Interpolator;
|
||||
import org.codehaus.plexus.interpolation.MapBasedValueSource;
|
||||
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
|
||||
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -155,6 +153,7 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
private final List<ModelTransformer> transformers;
|
||||
private final ModelCacheFactory modelCacheFactory;
|
||||
private final ModelResolver modelResolver;
|
||||
private final Interpolator interpolator;
|
||||
|
||||
@SuppressWarnings("checkstyle:ParameterNumber")
|
||||
@Inject
|
||||
|
@ -177,7 +176,8 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
ModelVersionParser versionParser,
|
||||
List<ModelTransformer> transformers,
|
||||
ModelCacheFactory modelCacheFactory,
|
||||
ModelResolver modelResolver) {
|
||||
ModelResolver modelResolver,
|
||||
Interpolator interpolator) {
|
||||
this.modelProcessor = modelProcessor;
|
||||
this.modelValidator = modelValidator;
|
||||
this.modelNormalizer = modelNormalizer;
|
||||
|
@ -197,6 +197,7 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
this.transformers = transformers;
|
||||
this.modelCacheFactory = modelCacheFactory;
|
||||
this.modelResolver = modelResolver;
|
||||
this.interpolator = interpolator;
|
||||
}
|
||||
|
||||
public ModelBuilderSession newSession() {
|
||||
|
@ -1652,6 +1653,73 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
private <T> T cache(Source source, String tag, Supplier<T> supplier) {
|
||||
return cache.computeIfAbsent(source, tag, supplier);
|
||||
}
|
||||
|
||||
private List<Profile> interpolateActivations(
|
||||
List<Profile> profiles, DefaultProfileActivationContext context, ModelProblemCollector problems) {
|
||||
if (profiles.stream()
|
||||
.map(org.apache.maven.api.model.Profile::getActivation)
|
||||
.noneMatch(Objects::nonNull)) {
|
||||
return profiles;
|
||||
}
|
||||
Interpolator interpolator = request.getSession().getService(Interpolator.class);
|
||||
|
||||
class ProfileInterpolator extends MavenTransformer implements UnaryOperator<Profile> {
|
||||
ProfileInterpolator() {
|
||||
super(s -> {
|
||||
try {
|
||||
Map<String, String> map1 = context.getUserProperties();
|
||||
Map<String, String> map2 = context.getSystemProperties();
|
||||
return interpolator.interpolate(s, Interpolator.chain(List.of(map1::get, map2::get)));
|
||||
} catch (InterpolatorException e) {
|
||||
problems.add(Severity.ERROR, Version.BASE, e.getMessage(), e);
|
||||
}
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Profile apply(Profile p) {
|
||||
return Profile.newBuilder(p)
|
||||
.activation(transformActivation(p.getActivation()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActivationFile.Builder transformActivationFile_Missing(
|
||||
Supplier<? extends ActivationFile.Builder> creator,
|
||||
ActivationFile.Builder builder,
|
||||
ActivationFile target) {
|
||||
String path = target.getMissing();
|
||||
String xformed = transformPath(path, target, "missing");
|
||||
return xformed != path ? (builder != null ? builder : creator.get()).missing(xformed) : builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActivationFile.Builder transformActivationFile_Exists(
|
||||
Supplier<? extends ActivationFile.Builder> creator,
|
||||
ActivationFile.Builder builder,
|
||||
ActivationFile target) {
|
||||
final String path = target.getExists();
|
||||
final String xformed = transformPath(path, target, "exists");
|
||||
return xformed != path ? (builder != null ? builder : creator.get()).exists(xformed) : builder;
|
||||
}
|
||||
|
||||
private String transformPath(String path, ActivationFile target, String locationKey) {
|
||||
try {
|
||||
return profileActivationFilePathInterpolator.interpolate(path, context);
|
||||
} catch (InterpolatorException e) {
|
||||
problems.add(
|
||||
Severity.ERROR,
|
||||
Version.BASE,
|
||||
"Failed to interpolate file location " + path + ": " + e.getMessage(),
|
||||
target.getLocation(locationKey),
|
||||
e);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return profiles.stream().map(new ProfileInterpolator()).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
|
@ -1663,83 +1731,6 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
return subprojects;
|
||||
}
|
||||
|
||||
private List<Profile> interpolateActivations(
|
||||
List<Profile> profiles, DefaultProfileActivationContext context, ModelProblemCollector problems) {
|
||||
if (profiles.stream()
|
||||
.map(org.apache.maven.api.model.Profile::getActivation)
|
||||
.noneMatch(Objects::nonNull)) {
|
||||
return profiles;
|
||||
}
|
||||
final Interpolator xform = new RegexBasedInterpolator();
|
||||
xform.setCacheAnswers(true);
|
||||
Stream.of(context.getUserProperties(), context.getSystemProperties())
|
||||
.map(MapBasedValueSource::new)
|
||||
.forEach(xform::addValueSource);
|
||||
|
||||
class ProfileInterpolator extends MavenTransformer implements UnaryOperator<Profile> {
|
||||
ProfileInterpolator() {
|
||||
super(s -> {
|
||||
if (isNotEmpty(s)) {
|
||||
try {
|
||||
return xform.interpolate(s);
|
||||
} catch (InterpolationException e) {
|
||||
problems.add(Severity.ERROR, Version.BASE, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Profile apply(Profile p) {
|
||||
return Profile.newBuilder(p)
|
||||
.activation(transformActivation(p.getActivation()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActivationFile.Builder transformActivationFile_Missing(
|
||||
Supplier<? extends ActivationFile.Builder> creator,
|
||||
ActivationFile.Builder builder,
|
||||
ActivationFile target) {
|
||||
String path = target.getMissing();
|
||||
String xformed = transformPath(path, target, "missing");
|
||||
return xformed != path ? (builder != null ? builder : creator.get()).missing(xformed) : builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActivationFile.Builder transformActivationFile_Exists(
|
||||
Supplier<? extends ActivationFile.Builder> creator,
|
||||
ActivationFile.Builder builder,
|
||||
ActivationFile target) {
|
||||
final String path = target.getExists();
|
||||
final String xformed = transformPath(path, target, "exists");
|
||||
return xformed != path ? (builder != null ? builder : creator.get()).exists(xformed) : builder;
|
||||
}
|
||||
|
||||
private String transformPath(String path, ActivationFile target, String locationKey) {
|
||||
if (isNotEmpty(path)) {
|
||||
try {
|
||||
return profileActivationFilePathInterpolator.interpolate(path, context);
|
||||
} catch (InterpolationException e) {
|
||||
problems.add(
|
||||
Severity.ERROR,
|
||||
Version.BASE,
|
||||
"Failed to interpolate file location " + path + ": " + e.getMessage(),
|
||||
target.getLocation(locationKey),
|
||||
e);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return profiles.stream().map(new ProfileInterpolator()).toList();
|
||||
}
|
||||
|
||||
private static boolean isNotEmpty(String string) {
|
||||
return string != null && !string.isEmpty();
|
||||
}
|
||||
|
||||
public Model buildRawModel(ModelBuilderRequest request) throws ModelBuilderException {
|
||||
DefaultModelBuilderSession build = new DefaultModelBuilderSession(request);
|
||||
Model model = build.readRawModel();
|
||||
|
@ -1807,13 +1798,13 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
Model interpolatedModel =
|
||||
modelInterpolator.interpolateModel(model, model.getProjectDirectory(), request, problems);
|
||||
if (interpolatedModel.getParent() != null) {
|
||||
StringSearchInterpolator ssi = new StringSearchInterpolator();
|
||||
ssi.addValueSource(new MapBasedValueSource(request.getSession().getUserProperties()));
|
||||
ssi.addValueSource(new MapBasedValueSource(model.getProperties()));
|
||||
ssi.addValueSource(new MapBasedValueSource(request.getSession().getSystemProperties()));
|
||||
Map<String, String> map1 = request.getSession().getUserProperties();
|
||||
Map<String, String> map2 = model.getProperties();
|
||||
Map<String, String> map3 = request.getSession().getSystemProperties();
|
||||
Function<String, String> cb = Interpolator.chain(List.of(map1::get, map2::get, map3::get));
|
||||
try {
|
||||
String interpolated =
|
||||
ssi.interpolate(interpolatedModel.getParent().getVersion());
|
||||
interpolator.interpolate(interpolatedModel.getParent().getVersion(), cb);
|
||||
interpolatedModel = interpolatedModel.withParent(
|
||||
interpolatedModel.getParent().withVersion(interpolated));
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -18,24 +18,24 @@
|
|||
*/
|
||||
package org.apache.maven.internal.impl.model;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.maven.api.di.Inject;
|
||||
import org.apache.maven.api.di.Named;
|
||||
import org.apache.maven.api.di.Singleton;
|
||||
import org.apache.maven.api.model.Model;
|
||||
import org.apache.maven.api.services.BuilderProblem;
|
||||
import org.apache.maven.api.services.Interpolator;
|
||||
import org.apache.maven.api.services.InterpolatorException;
|
||||
import org.apache.maven.api.services.ModelBuilderRequest;
|
||||
import org.apache.maven.api.services.ModelProblem;
|
||||
import org.apache.maven.api.services.ModelProblemCollector;
|
||||
|
@ -43,20 +43,8 @@ import org.apache.maven.api.services.model.ModelInterpolator;
|
|||
import org.apache.maven.api.services.model.PathTranslator;
|
||||
import org.apache.maven.api.services.model.RootLocator;
|
||||
import org.apache.maven.api.services.model.UrlNormalizer;
|
||||
import org.apache.maven.internal.impl.model.reflection.ReflectionValueExtractor;
|
||||
import org.apache.maven.model.v4.MavenTransformer;
|
||||
import org.codehaus.plexus.interpolation.AbstractDelegatingValueSource;
|
||||
import org.codehaus.plexus.interpolation.AbstractValueSource;
|
||||
import org.codehaus.plexus.interpolation.InterpolationException;
|
||||
import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
|
||||
import org.codehaus.plexus.interpolation.MapBasedValueSource;
|
||||
import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
|
||||
import org.codehaus.plexus.interpolation.PrefixedValueSourceWrapper;
|
||||
import org.codehaus.plexus.interpolation.QueryEnabledValueSource;
|
||||
import org.codehaus.plexus.interpolation.RecursionInterceptor;
|
||||
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
|
||||
import org.codehaus.plexus.interpolation.ValueSource;
|
||||
import org.codehaus.plexus.interpolation.reflection.ReflectionValueExtractor;
|
||||
import org.codehaus.plexus.interpolation.util.ValueSourceUtils;
|
||||
|
||||
@Named
|
||||
@Singleton
|
||||
|
@ -67,37 +55,42 @@ public class DefaultModelInterpolator implements ModelInterpolator {
|
|||
private static final List<String> PROJECT_PREFIXES_3_1 = Arrays.asList(PREFIX_POM, PREFIX_PROJECT);
|
||||
private static final List<String> PROJECT_PREFIXES_4_0 = Collections.singletonList(PREFIX_PROJECT);
|
||||
|
||||
private static final Collection<String> TRANSLATED_PATH_EXPRESSIONS;
|
||||
// MNG-1927, MNG-2124, MNG-3355:
|
||||
// If the build section is present and the project directory is non-null, we should make
|
||||
// sure interpolation of the directories below uses translated paths.
|
||||
// Afterward, we'll double back and translate any paths that weren't covered during interpolation via the
|
||||
// code below...
|
||||
private static final Set<String> TRANSLATED_PATH_EXPRESSIONS = Set.of(
|
||||
"build.directory",
|
||||
"build.outputDirectory",
|
||||
"build.testOutputDirectory",
|
||||
"build.sourceDirectory",
|
||||
"build.testSourceDirectory",
|
||||
"build.scriptSourceDirectory",
|
||||
"reporting.outputDirectory");
|
||||
|
||||
static {
|
||||
Collection<String> translatedPrefixes = new HashSet<>();
|
||||
|
||||
// MNG-1927, MNG-2124, MNG-3355:
|
||||
// If the build section is present and the project directory is non-null, we should make
|
||||
// sure interpolation of the directories below uses translated paths.
|
||||
// Afterward, we'll double back and translate any paths that weren't covered during interpolation via the
|
||||
// code below...
|
||||
translatedPrefixes.add("build.directory");
|
||||
translatedPrefixes.add("build.outputDirectory");
|
||||
translatedPrefixes.add("build.testOutputDirectory");
|
||||
translatedPrefixes.add("build.sourceDirectory");
|
||||
translatedPrefixes.add("build.testSourceDirectory");
|
||||
translatedPrefixes.add("build.scriptSourceDirectory");
|
||||
translatedPrefixes.add("reporting.outputDirectory");
|
||||
|
||||
TRANSLATED_PATH_EXPRESSIONS = translatedPrefixes;
|
||||
}
|
||||
private static final Set<String> URL_EXPRESSIONS = Set.of(
|
||||
"project.url",
|
||||
"project.scm.url",
|
||||
"project.scm.connection",
|
||||
"project.scm.developerConnection",
|
||||
"project.distributionManagement.site.url");
|
||||
|
||||
private final PathTranslator pathTranslator;
|
||||
private final UrlNormalizer urlNormalizer;
|
||||
private final RootLocator rootLocator;
|
||||
private final Interpolator interpolator;
|
||||
|
||||
@Inject
|
||||
public DefaultModelInterpolator(
|
||||
PathTranslator pathTranslator, UrlNormalizer urlNormalizer, RootLocator rootLocator) {
|
||||
PathTranslator pathTranslator,
|
||||
UrlNormalizer urlNormalizer,
|
||||
RootLocator rootLocator,
|
||||
Interpolator interpolator) {
|
||||
this.pathTranslator = pathTranslator;
|
||||
this.urlNormalizer = urlNormalizer;
|
||||
this.rootLocator = rootLocator;
|
||||
this.interpolator = interpolator;
|
||||
}
|
||||
|
||||
interface InnerInterpolator {
|
||||
|
@ -107,43 +100,25 @@ public class DefaultModelInterpolator implements ModelInterpolator {
|
|||
@Override
|
||||
public Model interpolateModel(
|
||||
Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems) {
|
||||
List<? extends ValueSource> valueSources = createValueSources(model, projectDir, request, problems);
|
||||
List<? extends InterpolationPostProcessor> postProcessors = createPostProcessors(model, projectDir, request);
|
||||
|
||||
InnerInterpolator innerInterpolator = createInterpolator(valueSources, postProcessors, request, problems);
|
||||
|
||||
InnerInterpolator innerInterpolator = createInterpolator(model, projectDir, request, problems);
|
||||
return new MavenTransformer(innerInterpolator::interpolate).visit(model);
|
||||
}
|
||||
|
||||
private InnerInterpolator createInterpolator(
|
||||
List<? extends ValueSource> valueSources,
|
||||
List<? extends InterpolationPostProcessor> postProcessors,
|
||||
ModelBuilderRequest request,
|
||||
ModelProblemCollector problems) {
|
||||
Map<String, String> cache = new HashMap<>();
|
||||
StringSearchInterpolator interpolator = new StringSearchInterpolator();
|
||||
interpolator.setCacheAnswers(true);
|
||||
for (ValueSource vs : valueSources) {
|
||||
interpolator.addValueSource(vs);
|
||||
}
|
||||
for (InterpolationPostProcessor postProcessor : postProcessors) {
|
||||
interpolator.addPostProcessor(postProcessor);
|
||||
}
|
||||
RecursionInterceptor recursionInterceptor = createRecursionInterceptor(request);
|
||||
Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems) {
|
||||
|
||||
Map<String, Optional<String>> cache = new HashMap<>();
|
||||
Function<String, Optional<String>> ucb =
|
||||
v -> Optional.ofNullable(callback(model, projectDir, request, problems, v));
|
||||
Function<String, String> cb = v -> cache.computeIfAbsent(v, ucb).orElse(null);
|
||||
BiFunction<String, String, String> postprocessor = (e, v) -> postProcess(projectDir, request, e, v);
|
||||
return value -> {
|
||||
if (value != null && value.contains("${")) {
|
||||
String c = cache.get(value);
|
||||
if (c == null) {
|
||||
try {
|
||||
c = interpolator.interpolate(value, recursionInterceptor);
|
||||
} catch (InterpolationException e) {
|
||||
problems.add(BuilderProblem.Severity.ERROR, ModelProblem.Version.BASE, e.getMessage(), e);
|
||||
}
|
||||
cache.put(value, c);
|
||||
}
|
||||
return c;
|
||||
try {
|
||||
return interpolator.interpolate(value, cb, postprocessor, false);
|
||||
} catch (InterpolatorException e) {
|
||||
problems.add(BuilderProblem.Severity.ERROR, ModelProblem.Version.BASE, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -153,332 +128,130 @@ public class DefaultModelInterpolator implements ModelInterpolator {
|
|||
: PROJECT_PREFIXES_3_1;
|
||||
}
|
||||
|
||||
protected List<ValueSource> createValueSources(
|
||||
Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems) {
|
||||
Map<String, String> modelProperties = model.getProperties();
|
||||
|
||||
ValueSource projectPrefixValueSource;
|
||||
ValueSource prefixlessObjectBasedValueSource;
|
||||
if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_POM) {
|
||||
projectPrefixValueSource = new PrefixedObjectValueSource(PROJECT_PREFIXES_4_0, model, false);
|
||||
prefixlessObjectBasedValueSource = new ObjectBasedValueSource(model);
|
||||
} else {
|
||||
projectPrefixValueSource = new PrefixedObjectValueSource(PROJECT_PREFIXES_3_1, model, false);
|
||||
projectPrefixValueSource =
|
||||
new ProblemDetectingValueSource(projectPrefixValueSource, PREFIX_POM, PREFIX_PROJECT, problems);
|
||||
|
||||
prefixlessObjectBasedValueSource = new ObjectBasedValueSource(model);
|
||||
prefixlessObjectBasedValueSource =
|
||||
new ProblemDetectingValueSource(prefixlessObjectBasedValueSource, "", PREFIX_PROJECT, problems);
|
||||
String callback(
|
||||
Model model,
|
||||
Path projectDir,
|
||||
ModelBuilderRequest request,
|
||||
ModelProblemCollector problems,
|
||||
String expression) {
|
||||
String value = doCallback(model, projectDir, request, problems, expression);
|
||||
if (value != null) {
|
||||
// value = postProcess(projectDir, request, expression, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// NOTE: Order counts here!
|
||||
List<ValueSource> valueSources = new ArrayList<>(9);
|
||||
|
||||
if (projectDir != null) {
|
||||
ValueSource basedirValueSource = new PrefixedValueSourceWrapper(
|
||||
new AbstractValueSource(false) {
|
||||
@Override
|
||||
public Object getValue(String expression) {
|
||||
if ("basedir".equals(expression)) {
|
||||
return projectDir.toAbsolutePath().toString();
|
||||
} else if (expression.startsWith("basedir.")) {
|
||||
Path basedir = projectDir.toAbsolutePath();
|
||||
return new ObjectBasedValueSource(basedir)
|
||||
.getValue(expression.substring("basedir.".length()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getProjectPrefixes(request),
|
||||
true);
|
||||
valueSources.add(basedirValueSource);
|
||||
|
||||
ValueSource baseUriValueSource = new PrefixedValueSourceWrapper(
|
||||
new AbstractValueSource(false) {
|
||||
@Override
|
||||
public Object getValue(String expression) {
|
||||
if ("baseUri".equals(expression)) {
|
||||
return projectDir.toAbsolutePath().toUri().toASCIIString();
|
||||
} else if (expression.startsWith("baseUri.")) {
|
||||
URI baseUri = projectDir.toAbsolutePath().toUri();
|
||||
return new ObjectBasedValueSource(baseUri)
|
||||
.getValue(expression.substring("baseUri.".length()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getProjectPrefixes(request),
|
||||
false);
|
||||
valueSources.add(baseUriValueSource);
|
||||
valueSources.add(new BuildTimestampValueSource(request.getSession().getStartTime(), modelProperties));
|
||||
private String postProcess(Path projectDir, ModelBuilderRequest request, String expression, String value) {
|
||||
// path translation
|
||||
String exp = unprefix(expression, getProjectPrefixes(request));
|
||||
if (TRANSLATED_PATH_EXPRESSIONS.contains(exp)) {
|
||||
value = pathTranslator.alignToBaseDirectory(value, projectDir);
|
||||
}
|
||||
// normalize url
|
||||
if (URL_EXPRESSIONS.contains(expression)) {
|
||||
value = urlNormalizer.normalize(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
valueSources.add(new PrefixedValueSourceWrapper(
|
||||
new AbstractValueSource(false) {
|
||||
@Override
|
||||
public Object getValue(String expression) {
|
||||
if ("rootDirectory".equals(expression)) {
|
||||
Path root = rootLocator.findMandatoryRoot(projectDir);
|
||||
return root.toFile().getPath();
|
||||
} else if (expression.startsWith("rootDirectory.")) {
|
||||
Path root = rootLocator.findMandatoryRoot(projectDir);
|
||||
return new ObjectBasedValueSource(root)
|
||||
.getValue(expression.substring("rootDirectory.".length()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getProjectPrefixes(request)));
|
||||
|
||||
valueSources.add(projectPrefixValueSource);
|
||||
|
||||
valueSources.add(new MapBasedValueSource(request.getUserProperties()));
|
||||
|
||||
valueSources.add(new MapBasedValueSource(modelProperties));
|
||||
|
||||
valueSources.add(new MapBasedValueSource(request.getSystemProperties()));
|
||||
|
||||
valueSources.add(new AbstractValueSource(false) {
|
||||
@Override
|
||||
public Object getValue(String expression) {
|
||||
return request.getSystemProperties().get("env." + expression);
|
||||
private String unprefix(String expression, List<String> prefixes) {
|
||||
for (String prefix : prefixes) {
|
||||
if (expression.startsWith(prefix)) {
|
||||
return expression.substring(prefix.length());
|
||||
}
|
||||
});
|
||||
|
||||
valueSources.add(prefixlessObjectBasedValueSource);
|
||||
|
||||
return valueSources;
|
||||
}
|
||||
|
||||
protected List<? extends InterpolationPostProcessor> createPostProcessors(
|
||||
Model model, Path projectDir, ModelBuilderRequest request) {
|
||||
List<InterpolationPostProcessor> processors = new ArrayList<>(2);
|
||||
if (projectDir != null) {
|
||||
processors.add(new PathTranslatingPostProcessor(
|
||||
getProjectPrefixes(request), TRANSLATED_PATH_EXPRESSIONS, projectDir, pathTranslator));
|
||||
}
|
||||
processors.add(new UrlNormalizingPostProcessor(urlNormalizer));
|
||||
return processors;
|
||||
return expression;
|
||||
}
|
||||
|
||||
protected RecursionInterceptor createRecursionInterceptor(ModelBuilderRequest request) {
|
||||
return new PrefixAwareRecursionInterceptor(getProjectPrefixes(request));
|
||||
}
|
||||
|
||||
static class PathTranslatingPostProcessor implements InterpolationPostProcessor {
|
||||
|
||||
private final Collection<String> unprefixedPathKeys;
|
||||
private final Path projectDir;
|
||||
private final PathTranslator pathTranslator;
|
||||
private final List<String> expressionPrefixes;
|
||||
|
||||
PathTranslatingPostProcessor(
|
||||
List<String> expressionPrefixes,
|
||||
Collection<String> unprefixedPathKeys,
|
||||
Path projectDir,
|
||||
PathTranslator pathTranslator) {
|
||||
this.expressionPrefixes = expressionPrefixes;
|
||||
this.unprefixedPathKeys = unprefixedPathKeys;
|
||||
this.projectDir = projectDir;
|
||||
this.pathTranslator = pathTranslator;
|
||||
String doCallback(
|
||||
Model model,
|
||||
Path projectDir,
|
||||
ModelBuilderRequest request,
|
||||
ModelProblemCollector problems,
|
||||
String expression) {
|
||||
// timestamp
|
||||
if ("build.timestamp".equals(expression) || "maven.build.timestamp".equals(expression)) {
|
||||
return new MavenBuildTimestamp(request.getSession().getStartTime(), model.getProperties())
|
||||
.formattedTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(String expression, Object value) {
|
||||
if (value != null) {
|
||||
expression = ValueSourceUtils.trimPrefix(expression, expressionPrefixes, true);
|
||||
if (unprefixedPathKeys.contains(expression)) {
|
||||
return pathTranslator.alignToBaseDirectory(String.valueOf(value), projectDir);
|
||||
// prefixed model reflection
|
||||
for (String prefix : getProjectPrefixes(request)) {
|
||||
if (expression.startsWith(prefix)) {
|
||||
String subExpr = expression.substring(prefix.length());
|
||||
String v = projectProperty(model, projectDir, subExpr, true);
|
||||
if (v != null) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that expressions referring to URLs evaluate to normalized URLs.
|
||||
*
|
||||
*/
|
||||
static class UrlNormalizingPostProcessor implements InterpolationPostProcessor {
|
||||
|
||||
private static final Set<String> URL_EXPRESSIONS;
|
||||
|
||||
static {
|
||||
Set<String> expressions = new HashSet<>();
|
||||
expressions.add("project.url");
|
||||
expressions.add("project.scm.url");
|
||||
expressions.add("project.scm.connection");
|
||||
expressions.add("project.scm.developerConnection");
|
||||
expressions.add("project.distributionManagement.site.url");
|
||||
URL_EXPRESSIONS = expressions;
|
||||
// user properties
|
||||
String value = request.getUserProperties().get(expression);
|
||||
// model properties
|
||||
if (value == null) {
|
||||
value = model.getProperties().get(expression);
|
||||
}
|
||||
|
||||
private final UrlNormalizer normalizer;
|
||||
|
||||
UrlNormalizingPostProcessor(UrlNormalizer normalizer) {
|
||||
this.normalizer = normalizer;
|
||||
// system properties
|
||||
if (value == null) {
|
||||
value = request.getSystemProperties().get(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(String expression, Object value) {
|
||||
if (value != null && URL_EXPRESSIONS.contains(expression)) {
|
||||
return normalizer.normalize(value.toString());
|
||||
}
|
||||
|
||||
return null;
|
||||
// environment variables
|
||||
if (value == null) {
|
||||
value = request.getSystemProperties().get("env." + expression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an arbitrary object with an {@link ObjectBasedValueSource} instance, then
|
||||
* wraps that source with a {@link PrefixedValueSourceWrapper} instance, to which
|
||||
* this class delegates all of its calls.
|
||||
*/
|
||||
public static class PrefixedObjectValueSource extends AbstractDelegatingValueSource
|
||||
implements QueryEnabledValueSource {
|
||||
|
||||
/**
|
||||
* Wrap the specified root object, allowing the specified expression prefix.
|
||||
* @param prefix the prefix.
|
||||
* @param root the root of the graph.
|
||||
*/
|
||||
public PrefixedObjectValueSource(String prefix, Object root) {
|
||||
super(new PrefixedValueSourceWrapper(new ObjectBasedValueSource(root), prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the specified root object, allowing the specified list of expression
|
||||
* prefixes and setting whether the {@link PrefixedValueSourceWrapper} allows
|
||||
* unprefixed expressions.
|
||||
* @param possiblePrefixes The possible prefixes.
|
||||
* @param root The root of the graph.
|
||||
* @param allowUnprefixedExpressions if we allow undefined expressions or not.
|
||||
*/
|
||||
public PrefixedObjectValueSource(
|
||||
List<String> possiblePrefixes, Object root, boolean allowUnprefixedExpressions) {
|
||||
super(new PrefixedValueSourceWrapper(
|
||||
new ObjectBasedValueSource(root), possiblePrefixes, allowUnprefixedExpressions));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String getLastExpression() {
|
||||
return ((QueryEnabledValueSource) getDelegate()).getLastExpression();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an object, providing reflective access to the object graph of which the
|
||||
* supplied object is the root. Expressions like 'child.name' will translate into
|
||||
* 'rootObject.getChild().getName()' for non-boolean properties, and
|
||||
* 'rootObject.getChild().isName()' for boolean properties.
|
||||
*/
|
||||
public static class ObjectBasedValueSource extends AbstractValueSource {
|
||||
|
||||
private final Object root;
|
||||
|
||||
/**
|
||||
* Construct a new value source, using the supplied object as the root from
|
||||
* which to start, and using expressions split at the dot ('.') to navigate
|
||||
* the object graph beneath this root.
|
||||
* @param root the root of the graph.
|
||||
*/
|
||||
public ObjectBasedValueSource(Object root) {
|
||||
super(true);
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Split the expression into parts, tokenized on the dot ('.') character. Then,
|
||||
* starting at the root object contained in this value source, apply each part
|
||||
* to the object graph below this root, using either 'getXXX()' or 'isXXX()'
|
||||
* accessor types to resolve the value for each successive expression part.
|
||||
* Finally, return the result of the last expression part's resolution.</p>
|
||||
*
|
||||
* <p><b>NOTE:</b> The object-graph nagivation actually takes place via the
|
||||
* {@link ReflectionValueExtractor} class.</p>
|
||||
*/
|
||||
public Object getValue(String expression) {
|
||||
if (expression == null || expression.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return ReflectionValueExtractor.evaluate(expression, root, false);
|
||||
} catch (Exception e) {
|
||||
addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps another value source and intercepts interpolated expressions, checking for problems.
|
||||
*
|
||||
*/
|
||||
static class ProblemDetectingValueSource implements ValueSource {
|
||||
|
||||
private final ValueSource valueSource;
|
||||
|
||||
private final String bannedPrefix;
|
||||
|
||||
private final String newPrefix;
|
||||
|
||||
private final ModelProblemCollector problems;
|
||||
|
||||
ProblemDetectingValueSource(
|
||||
ValueSource valueSource, String bannedPrefix, String newPrefix, ModelProblemCollector problems) {
|
||||
this.valueSource = valueSource;
|
||||
this.bannedPrefix = bannedPrefix;
|
||||
this.newPrefix = newPrefix;
|
||||
this.problems = problems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(String expression) {
|
||||
Object value = valueSource.getValue(expression);
|
||||
|
||||
if (value != null && expression.startsWith(bannedPrefix)) {
|
||||
String msg = "The expression ${" + expression + "} is deprecated.";
|
||||
if (newPrefix != null && !newPrefix.isEmpty()) {
|
||||
msg += " Please use ${" + newPrefix + expression.substring(bannedPrefix.length()) + "} instead.";
|
||||
}
|
||||
problems.add(BuilderProblem.Severity.WARNING, ModelProblem.Version.V20, msg);
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List getFeedback() {
|
||||
return valueSource.getFeedback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearFeedback() {
|
||||
valueSource.clearFeedback();
|
||||
}
|
||||
// model reflection
|
||||
return projectProperty(model, projectDir, expression, false);
|
||||
}
|
||||
|
||||
static class BuildTimestampValueSource extends AbstractValueSource {
|
||||
private final Instant startTime;
|
||||
private final Map<String, String> properties;
|
||||
|
||||
BuildTimestampValueSource(Instant startTime, Map<String, String> properties) {
|
||||
super(false);
|
||||
this.startTime = startTime;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(String expression) {
|
||||
if ("build.timestamp".equals(expression) || "maven.build.timestamp".equals(expression)) {
|
||||
return new MavenBuildTimestamp(startTime, properties).formattedTimestamp();
|
||||
String projectProperty(Model model, Path projectDir, String subExpr, boolean prefixed) {
|
||||
if (projectDir != null) {
|
||||
if (subExpr.equals("basedir")) {
|
||||
return projectDir.toAbsolutePath().toString();
|
||||
} else if (subExpr.startsWith("basedir.")) {
|
||||
try {
|
||||
Object value = ReflectionValueExtractor.evaluate(subExpr, projectDir.toAbsolutePath(), false);
|
||||
if (value != null) {
|
||||
return value.toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
|
||||
}
|
||||
} else if (prefixed && subExpr.equals("baseUri")) {
|
||||
return projectDir.toAbsolutePath().toUri().toASCIIString();
|
||||
} else if (prefixed && subExpr.startsWith("baseUri.")) {
|
||||
try {
|
||||
Object value = ReflectionValueExtractor.evaluate(
|
||||
subExpr, projectDir.toAbsolutePath().toUri(), false);
|
||||
if (value != null) {
|
||||
return value.toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
|
||||
}
|
||||
} else if (prefixed && subExpr.equals("rootDirectory")) {
|
||||
return rootLocator.findMandatoryRoot(projectDir).toString();
|
||||
} else if (prefixed && subExpr.startsWith("rootDirectory.")) {
|
||||
try {
|
||||
Object value = ReflectionValueExtractor.evaluate(
|
||||
subExpr, projectDir.toAbsolutePath().toUri(), false);
|
||||
if (value != null) {
|
||||
return value.toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Object value = ReflectionValueExtractor.evaluate(subExpr, model, false);
|
||||
if (value != null) {
|
||||
return value.toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,17 +24,14 @@ import org.apache.maven.api.di.Inject;
|
|||
import org.apache.maven.api.di.Named;
|
||||
import org.apache.maven.api.di.Singleton;
|
||||
import org.apache.maven.api.model.ActivationFile;
|
||||
import org.apache.maven.api.services.Interpolator;
|
||||
import org.apache.maven.api.services.InterpolatorException;
|
||||
import org.apache.maven.api.services.model.PathTranslator;
|
||||
import org.apache.maven.api.services.model.ProfileActivationContext;
|
||||
import org.apache.maven.api.services.model.RootLocator;
|
||||
import org.codehaus.plexus.interpolation.AbstractValueSource;
|
||||
import org.codehaus.plexus.interpolation.InterpolationException;
|
||||
import org.codehaus.plexus.interpolation.MapBasedValueSource;
|
||||
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
|
||||
|
||||
/**
|
||||
* Finds an absolute path for {@link ActivationFile#getExists()} or {@link ActivationFile#getMissing()}
|
||||
*
|
||||
*/
|
||||
@Named
|
||||
@Singleton
|
||||
|
@ -44,10 +41,14 @@ public class ProfileActivationFilePathInterpolator {
|
|||
|
||||
private final RootLocator rootLocator;
|
||||
|
||||
private final Interpolator interpolator;
|
||||
|
||||
@Inject
|
||||
public ProfileActivationFilePathInterpolator(PathTranslator pathTranslator, RootLocator rootLocator) {
|
||||
public ProfileActivationFilePathInterpolator(
|
||||
PathTranslator pathTranslator, RootLocator rootLocator, Interpolator interpolator) {
|
||||
this.pathTranslator = pathTranslator;
|
||||
this.rootLocator = rootLocator;
|
||||
this.interpolator = interpolator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,46 +56,31 @@ public class ProfileActivationFilePathInterpolator {
|
|||
*
|
||||
* @return absolute path or {@code null} if the input was {@code null}
|
||||
*/
|
||||
public String interpolate(String path, ProfileActivationContext context) throws InterpolationException {
|
||||
public String interpolate(String path, ProfileActivationContext context) throws InterpolatorException {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
|
||||
|
||||
Path basedir = context.getProjectDirectory();
|
||||
|
||||
if (basedir != null) {
|
||||
interpolator.addValueSource(new AbstractValueSource(false) {
|
||||
@Override
|
||||
public Object getValue(String expression) {
|
||||
if ("basedir".equals(expression) || "project.basedir".equals(expression)) {
|
||||
return basedir.toAbsolutePath().toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} else if (path.contains("${basedir}")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
interpolator.addValueSource(new AbstractValueSource(false) {
|
||||
@Override
|
||||
public Object getValue(String expression) {
|
||||
if ("project.rootDirectory".equals(expression)) {
|
||||
Path root = rootLocator.findMandatoryRoot(basedir);
|
||||
return root.toFile().getAbsolutePath();
|
||||
}
|
||||
return null;
|
||||
String absolutePath = interpolator.interpolate(path, s -> {
|
||||
if ("basedir".equals(s) || "project.basedir".equals(s)) {
|
||||
return basedir != null ? basedir.toFile().getAbsolutePath() : null;
|
||||
}
|
||||
if ("project.rootDirectory".equals(s)) {
|
||||
Path root = rootLocator.findMandatoryRoot(basedir);
|
||||
return root.toFile().getAbsolutePath();
|
||||
}
|
||||
String r = context.getProjectProperties().get(s);
|
||||
if (r == null) {
|
||||
r = context.getUserProperties().get(s);
|
||||
}
|
||||
if (r == null) {
|
||||
r = context.getSystemProperties().get(s);
|
||||
}
|
||||
return r;
|
||||
});
|
||||
|
||||
interpolator.addValueSource(new MapBasedValueSource(context.getProjectProperties()));
|
||||
interpolator.addValueSource(new MapBasedValueSource(context.getUserProperties()));
|
||||
interpolator.addValueSource(new MapBasedValueSource(context.getSystemProperties()));
|
||||
|
||||
String absolutePath = interpolator.interpolate(path, "");
|
||||
|
||||
return pathTranslator.alignToBaseDirectory(absolutePath, basedir);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,12 +27,12 @@ import org.apache.maven.api.model.Activation;
|
|||
import org.apache.maven.api.model.ActivationFile;
|
||||
import org.apache.maven.api.model.Profile;
|
||||
import org.apache.maven.api.services.BuilderProblem;
|
||||
import org.apache.maven.api.services.InterpolatorException;
|
||||
import org.apache.maven.api.services.ModelProblem;
|
||||
import org.apache.maven.api.services.ModelProblemCollector;
|
||||
import org.apache.maven.api.services.model.ProfileActivationContext;
|
||||
import org.apache.maven.api.services.model.ProfileActivator;
|
||||
import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolator;
|
||||
import org.codehaus.plexus.interpolation.InterpolationException;
|
||||
|
||||
/**
|
||||
* Determines profile activation based on the existence/absence of some file.
|
||||
|
@ -82,7 +82,7 @@ public class FileProfileActivator implements ProfileActivator {
|
|||
|
||||
try {
|
||||
path = profileActivationFilePathInterpolator.interpolate(path, context);
|
||||
} catch (InterpolationException e) {
|
||||
} catch (InterpolatorException e) {
|
||||
problems.add(
|
||||
BuilderProblem.Severity.ERROR,
|
||||
ModelProblem.Version.BASE,
|
||||
|
|
|
@ -0,0 +1,388 @@
|
|||
/*
|
||||
* 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.internal.impl.model.reflection;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A cache of introspection information for a specific class instance.
|
||||
* Keys {@link Method} objects by a concatenation of the
|
||||
* method name and the names of classes that make up the parameters.
|
||||
*/
|
||||
class ClassMap {
|
||||
private static final class CacheMiss {}
|
||||
|
||||
private static final CacheMiss CACHE_MISS = new CacheMiss();
|
||||
|
||||
private static final Object OBJECT = new Object();
|
||||
|
||||
/**
|
||||
* Class passed into the constructor used to as
|
||||
* the basis for the Method map.
|
||||
*/
|
||||
private final Class<?> clazz;
|
||||
|
||||
/**
|
||||
* Cache of Methods, or CACHE_MISS, keyed by method
|
||||
* name and actual arguments used to find it.
|
||||
*/
|
||||
private final Map<String, Object> methodCache = new Hashtable<>();
|
||||
|
||||
private MethodMap methodMap = new MethodMap();
|
||||
|
||||
/**
|
||||
* Standard constructor
|
||||
* @param clazz The class.
|
||||
*/
|
||||
ClassMap(Class<?> clazz) {
|
||||
this.clazz = clazz;
|
||||
populateMethodCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the class object whose methods are cached by this map.
|
||||
*/
|
||||
Class<?> getCachedClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Find a Method using the methodKey provided.</p>
|
||||
* <p>Look in the methodMap for an entry. If found,
|
||||
* it'll either be a CACHE_MISS, in which case we
|
||||
* simply give up, or it'll be a Method, in which
|
||||
* case, we return it.</p>
|
||||
* <p>If nothing is found, then we must actually go
|
||||
* and introspect the method from the MethodMap.</p>
|
||||
* @param name Method name.
|
||||
* @param params Method parameters.
|
||||
* @return The found method.
|
||||
* @throws MethodMap.AmbiguousException in case of duplicate methods.
|
||||
*/
|
||||
public Method findMethod(String name, Object... params) throws MethodMap.AmbiguousException {
|
||||
String methodKey = makeMethodKey(name, params);
|
||||
Object cacheEntry = methodCache.get(methodKey);
|
||||
|
||||
if (cacheEntry == CACHE_MISS) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cacheEntry == null) {
|
||||
try {
|
||||
cacheEntry = methodMap.find(name, params);
|
||||
} catch (MethodMap.AmbiguousException ae) {
|
||||
// that's a miss :)
|
||||
methodCache.put(methodKey, CACHE_MISS);
|
||||
throw ae;
|
||||
}
|
||||
|
||||
if (cacheEntry == null) {
|
||||
methodCache.put(methodKey, CACHE_MISS);
|
||||
} else {
|
||||
methodCache.put(methodKey, cacheEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Yes, this might just be null.
|
||||
return (Method) cacheEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the Map of direct hits. These
|
||||
* are taken from all the public methods
|
||||
* that our class provides.
|
||||
*/
|
||||
private void populateMethodCache() {
|
||||
// get all publicly accessible methods
|
||||
Method[] methods = getAccessibleMethods(clazz);
|
||||
|
||||
// map and cache them
|
||||
for (Method method : methods) {
|
||||
// now get the 'public method', the method declared by a
|
||||
// public interface or class (because the actual implementing
|
||||
// class may be a facade...)
|
||||
|
||||
Method publicMethod = getPublicMethod(method);
|
||||
|
||||
// it is entirely possible that there is no public method for
|
||||
// the methods of this class (i.e. in the facade, a method
|
||||
// that isn't on any of the interfaces or superclass
|
||||
// in which case, ignore it. Otherwise, map and cache
|
||||
if (publicMethod != null) {
|
||||
methodMap.add(publicMethod);
|
||||
methodCache.put(makeMethodKey(publicMethod), publicMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a methodKey for the given method using
|
||||
* the concatenation of the name and the
|
||||
* types of the method parameters.
|
||||
*/
|
||||
private String makeMethodKey(Method method) {
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
StringBuilder methodKey = new StringBuilder(method.getName());
|
||||
|
||||
for (Class<?> parameterType : parameterTypes) {
|
||||
// If the argument type is primitive then we want
|
||||
// to convert our primitive type signature to the
|
||||
// corresponding Object type so introspection for
|
||||
// methods with primitive types will work correctly.
|
||||
if (parameterType.isPrimitive()) {
|
||||
if (parameterType.equals(Boolean.TYPE)) {
|
||||
methodKey.append("java.lang.Boolean");
|
||||
} else if (parameterType.equals(Byte.TYPE)) {
|
||||
methodKey.append("java.lang.Byte");
|
||||
} else if (parameterType.equals(Character.TYPE)) {
|
||||
methodKey.append("java.lang.Character");
|
||||
} else if (parameterType.equals(Double.TYPE)) {
|
||||
methodKey.append("java.lang.Double");
|
||||
} else if (parameterType.equals(Float.TYPE)) {
|
||||
methodKey.append("java.lang.Float");
|
||||
} else if (parameterType.equals(Integer.TYPE)) {
|
||||
methodKey.append("java.lang.Integer");
|
||||
} else if (parameterType.equals(Long.TYPE)) {
|
||||
methodKey.append("java.lang.Long");
|
||||
} else if (parameterType.equals(Short.TYPE)) {
|
||||
methodKey.append("java.lang.Short");
|
||||
}
|
||||
} else {
|
||||
methodKey.append(parameterType.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return methodKey.toString();
|
||||
}
|
||||
|
||||
private static String makeMethodKey(String method, Object... params) {
|
||||
StringBuilder methodKey = new StringBuilder().append(method);
|
||||
|
||||
for (Object param : params) {
|
||||
Object arg = param;
|
||||
|
||||
if (arg == null) {
|
||||
arg = OBJECT;
|
||||
}
|
||||
|
||||
methodKey.append(arg.getClass().getName());
|
||||
}
|
||||
|
||||
return methodKey.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves public methods for a class. In case the class is not
|
||||
* public, retrieves methods with same signature as its public methods
|
||||
* from public superclasses and interfaces (if they exist). Basically
|
||||
* upcasts every method to the nearest acccessible method.
|
||||
*/
|
||||
private static Method[] getAccessibleMethods(Class<?> clazz) {
|
||||
Method[] methods = clazz.getMethods();
|
||||
|
||||
// Short circuit for the (hopefully) majority of cases where the
|
||||
// clazz is public
|
||||
if (Modifier.isPublic(clazz.getModifiers())) {
|
||||
return methods;
|
||||
}
|
||||
|
||||
// No luck - the class is not public, so we're going the longer way.
|
||||
MethodInfo[] methodInfos = new MethodInfo[methods.length];
|
||||
for (int i = methods.length; i-- > 0; ) {
|
||||
methodInfos[i] = new MethodInfo(methods[i]);
|
||||
}
|
||||
|
||||
int upcastCount = getAccessibleMethods(clazz, methodInfos, 0);
|
||||
|
||||
// Reallocate array in case some method had no accessible counterpart.
|
||||
if (upcastCount < methods.length) {
|
||||
methods = new Method[upcastCount];
|
||||
}
|
||||
|
||||
int j = 0;
|
||||
for (MethodInfo methodInfo : methodInfos) {
|
||||
if (methodInfo.upcast) {
|
||||
methods[j++] = methodInfo.method;
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively finds a match for each method, starting with the class, and then
|
||||
* searching the superclass and interfaces.
|
||||
*
|
||||
* @param clazz Class to check
|
||||
* @param methodInfos array of methods we are searching to match
|
||||
* @param upcastCount current number of methods we have matched
|
||||
* @return count of matched methods
|
||||
*/
|
||||
private static int getAccessibleMethods(Class<?> clazz, MethodInfo[] methodInfos, int upcastCount) {
|
||||
int l = methodInfos.length;
|
||||
|
||||
// if this class is public, then check each of the currently
|
||||
// 'non-upcasted' methods to see if we have a match
|
||||
if (Modifier.isPublic(clazz.getModifiers())) {
|
||||
for (int i = 0; i < l && upcastCount < l; ++i) {
|
||||
try {
|
||||
MethodInfo methodInfo = methodInfos[i];
|
||||
if (!methodInfo.upcast) {
|
||||
methodInfo.tryUpcasting(clazz);
|
||||
upcastCount++;
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Intentionally ignored - it means it wasn't found in the current class
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Short circuit if all methods were upcast
|
||||
*/
|
||||
|
||||
if (upcastCount == l) {
|
||||
return upcastCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Examine superclass
|
||||
Class<?> superclazz = clazz.getSuperclass();
|
||||
if (superclazz != null) {
|
||||
upcastCount = getAccessibleMethods(superclazz, methodInfos, upcastCount);
|
||||
|
||||
// Short circuit if all methods were upcast
|
||||
if (upcastCount == l) {
|
||||
return upcastCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Examine interfaces. Note we do it even if superclazz == null.
|
||||
// This is redundant as currently java.lang.Object does not implement
|
||||
// any interfaces, however nothing guarantees it will not in the future.
|
||||
Class<?>[] interfaces = clazz.getInterfaces();
|
||||
for (int i = interfaces.length; i-- > 0; ) {
|
||||
upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount);
|
||||
|
||||
// Short circuit if all methods were upcast
|
||||
if (upcastCount == l) {
|
||||
return upcastCount;
|
||||
}
|
||||
}
|
||||
|
||||
return upcastCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given method, retrieves its publicly accessible counterpart.
|
||||
* This method will look for a method with same name
|
||||
* and signature declared in a public superclass or implemented interface of this
|
||||
* method's declaring class. This counterpart method is publicly callable.
|
||||
*
|
||||
* @param method a method whose publicly callable counterpart is requested.
|
||||
* @return the publicly callable counterpart method. Note that if the parameter
|
||||
* method is itself declared by a public class, this method is an identity
|
||||
* function.
|
||||
*/
|
||||
private static Method getPublicMethod(Method method) {
|
||||
Class<?> clazz = method.getDeclaringClass();
|
||||
|
||||
// Short circuit for (hopefully the majority of) cases where the declaring
|
||||
// class is public.
|
||||
if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
|
||||
return method;
|
||||
}
|
||||
|
||||
return getPublicMethod(clazz, method.getName(), method.getParameterTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the method with specified name and signature in the first public
|
||||
* superclass or implemented interface of the class.
|
||||
*
|
||||
* @param clazz the class whose method is sought
|
||||
* @param name the name of the method
|
||||
* @param paramTypes the classes of method parameters
|
||||
*/
|
||||
private static Method getPublicMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
|
||||
// if this class is public, then try to get it
|
||||
if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
|
||||
try {
|
||||
return clazz.getMethod(name, paramTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// If the class does not have the method, then neither its superclass
|
||||
// nor any of its interfaces has it so quickly return null.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// try the superclass
|
||||
Class<?> superclazz = clazz.getSuperclass();
|
||||
|
||||
if (superclazz != null) {
|
||||
Method superclazzMethod = getPublicMethod(superclazz, name, paramTypes);
|
||||
|
||||
if (superclazzMethod != null) {
|
||||
return superclazzMethod;
|
||||
}
|
||||
}
|
||||
|
||||
// and interfaces
|
||||
Class<?>[] interfaces = clazz.getInterfaces();
|
||||
|
||||
for (Class<?> anInterface : interfaces) {
|
||||
Method interfaceMethod = getPublicMethod(anInterface, name, paramTypes);
|
||||
|
||||
if (interfaceMethod != null) {
|
||||
return interfaceMethod;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for the iterative discovery process for public methods.
|
||||
*/
|
||||
private static final class MethodInfo {
|
||||
Method method;
|
||||
|
||||
String name;
|
||||
|
||||
Class<?>[] parameterTypes;
|
||||
|
||||
boolean upcast;
|
||||
|
||||
MethodInfo(Method method) {
|
||||
this.method = null;
|
||||
name = method.getName();
|
||||
parameterTypes = method.getParameterTypes();
|
||||
upcast = false;
|
||||
}
|
||||
|
||||
void tryUpcasting(Class<?> clazz) throws NoSuchMethodException {
|
||||
method = clazz.getMethod(name, parameterTypes);
|
||||
name = null;
|
||||
parameterTypes = null;
|
||||
upcast = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.internal.impl.model.reflection;
|
||||
|
||||
public class IntrospectionException extends Exception {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -6090771282553728784L;
|
||||
|
||||
IntrospectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
IntrospectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* 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.internal.impl.model.reflection;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class MethodMap {
|
||||
private static final int MORE_SPECIFIC = 0;
|
||||
|
||||
private static final int LESS_SPECIFIC = 1;
|
||||
|
||||
private static final int INCOMPARABLE = 2;
|
||||
|
||||
/**
|
||||
* Keep track of all methods with the same name.
|
||||
*/
|
||||
private final Map<String, List<Method>> methodByNameMap = new Hashtable<>();
|
||||
|
||||
/**
|
||||
* Add a method to a list of methods by name.
|
||||
* For a particular class we are keeping track
|
||||
* of all the methods with the same name.
|
||||
*
|
||||
* @param method The method
|
||||
*/
|
||||
void add(Method method) {
|
||||
String methodName = method.getName();
|
||||
|
||||
List<Method> l = get(methodName);
|
||||
|
||||
if (l == null) {
|
||||
l = new ArrayList<>();
|
||||
methodByNameMap.put(methodName, l);
|
||||
}
|
||||
|
||||
l.add(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of methods with the same name.
|
||||
*
|
||||
* @param key The name of the method.
|
||||
* @return List list of methods
|
||||
*/
|
||||
List<Method> get(String key) {
|
||||
return methodByNameMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a method. Attempts to find the
|
||||
* most specific applicable method using the
|
||||
* algorithm described in the JLS section
|
||||
* 15.12.2 (with the exception that it can't
|
||||
* distinguish a primitive type argument from
|
||||
* an object type argument, since in reflection
|
||||
* primitive type arguments are represented by
|
||||
* their object counterparts, so for an argument of
|
||||
* type (say) java.lang.Integer, it will not be able
|
||||
* to decide between a method that takes int and a
|
||||
* method that takes java.lang.Integer as a parameter.
|
||||
* <p>
|
||||
* This turns out to be a relatively rare case
|
||||
* where this is needed - however, functionality
|
||||
* like this is needed.
|
||||
*
|
||||
* @param methodName name of method
|
||||
* @param args the actual arguments with which the method is called
|
||||
* @return the most specific applicable method, or null if no
|
||||
* method is applicable.
|
||||
* @throws AmbiguousException if there is more than one maximally
|
||||
* specific applicable method
|
||||
*/
|
||||
Method find(String methodName, Object... args) throws AmbiguousException {
|
||||
List<Method> methodList = get(methodName);
|
||||
|
||||
if (methodList == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int l = args.length;
|
||||
Class<?>[] classes = new Class[l];
|
||||
|
||||
for (int i = 0; i < l; ++i) {
|
||||
Object arg = args[i];
|
||||
// if we are careful down below, a null argument goes in there
|
||||
// so we can know that the null was passed to the method
|
||||
classes[i] = arg == null ? null : arg.getClass();
|
||||
}
|
||||
|
||||
return getMostSpecific(methodList, classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* simple distinguishable exception, used when
|
||||
* we run across ambiguous overloading
|
||||
*/
|
||||
static class AmbiguousException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 751688436639650618L;
|
||||
}
|
||||
|
||||
private static Method getMostSpecific(List<Method> methods, Class<?>... classes) throws AmbiguousException {
|
||||
LinkedList<Method> applicables = getApplicables(methods, classes);
|
||||
|
||||
if (applicables.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (applicables.size() == 1) {
|
||||
return applicables.getFirst();
|
||||
}
|
||||
|
||||
// This list will contain the maximally specific methods. Hopefully at
|
||||
// the end of the below loop, the list will contain exactly one method,
|
||||
// (the most specific method) otherwise we have ambiguity.
|
||||
LinkedList<Method> maximals = new LinkedList<>();
|
||||
|
||||
for (Method app : applicables) {
|
||||
Class<?>[] appArgs = app.getParameterTypes();
|
||||
boolean lessSpecific = false;
|
||||
|
||||
for (Iterator<Method> maximal = maximals.iterator(); !lessSpecific && maximal.hasNext(); ) {
|
||||
Method max = maximal.next();
|
||||
|
||||
switch (moreSpecific(appArgs, max.getParameterTypes())) {
|
||||
case MORE_SPECIFIC:
|
||||
// This method is more specific than the previously
|
||||
// known maximally specific, so remove the old maximum.
|
||||
maximal.remove();
|
||||
break;
|
||||
|
||||
case LESS_SPECIFIC:
|
||||
// This method is less specific than some of the
|
||||
// currently known maximally specific methods, so we
|
||||
// won't add it into the set of maximally specific
|
||||
// methods
|
||||
lessSpecific = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if (!lessSpecific) {
|
||||
maximals.addLast(app);
|
||||
}
|
||||
}
|
||||
|
||||
if (maximals.size() > 1) {
|
||||
// We have more than one maximally specific method
|
||||
throw new AmbiguousException();
|
||||
}
|
||||
|
||||
return maximals.getFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which method signature (represented by a class array) is more
|
||||
* specific. This defines a partial ordering on the method signatures.
|
||||
*
|
||||
* @param c1 first signature to compare
|
||||
* @param c2 second signature to compare
|
||||
* @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
|
||||
* c1 is less specific than c2, INCOMPARABLE if they are incomparable.
|
||||
*/
|
||||
private static int moreSpecific(Class<?>[] c1, Class<?>[] c2) {
|
||||
boolean c1MoreSpecific = false;
|
||||
boolean c2MoreSpecific = false;
|
||||
|
||||
for (int i = 0; i < c1.length; ++i) {
|
||||
if (c1[i] != c2[i]) {
|
||||
c1MoreSpecific = c1MoreSpecific || isStrictMethodInvocationConvertible(c2[i], c1[i]);
|
||||
c2MoreSpecific = c2MoreSpecific || isStrictMethodInvocationConvertible(c1[i], c2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (c1MoreSpecific) {
|
||||
if (c2MoreSpecific) {
|
||||
// Incomparable due to cross-assignable arguments (i.e.
|
||||
// foo(String, Object) vs. foo(Object, String))
|
||||
return INCOMPARABLE;
|
||||
}
|
||||
|
||||
return MORE_SPECIFIC;
|
||||
}
|
||||
|
||||
if (c2MoreSpecific) {
|
||||
return LESS_SPECIFIC;
|
||||
}
|
||||
|
||||
// Incomparable due to non-related arguments (i.e.
|
||||
// foo(Runnable) vs. foo(Serializable))
|
||||
return INCOMPARABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all methods that are applicable to actual argument types.
|
||||
*
|
||||
* @param methods list of all candidate methods
|
||||
* @param classes the actual types of the arguments
|
||||
* @return a list that contains only applicable methods (number of
|
||||
* formal and actual arguments matches, and argument types are assignable
|
||||
* to formal types through a method invocation conversion).
|
||||
*/
|
||||
private static LinkedList<Method> getApplicables(List<Method> methods, Class<?>... classes) {
|
||||
LinkedList<Method> list = new LinkedList<>();
|
||||
|
||||
for (Method method : methods) {
|
||||
if (isApplicable(method, classes)) {
|
||||
list.add(method);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the supplied method is applicable to actual
|
||||
* argument types.
|
||||
*
|
||||
* @param method The method to check for applicability
|
||||
* @param classes The arguments
|
||||
* @return true if the method applies to the parameter types
|
||||
*/
|
||||
private static boolean isApplicable(Method method, Class<?>... classes) {
|
||||
Class<?>[] methodArgs = method.getParameterTypes();
|
||||
|
||||
if (methodArgs.length != classes.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < classes.length; ++i) {
|
||||
if (!isMethodInvocationConvertible(methodArgs[i], classes[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a type represented by a class object is
|
||||
* convertible to another type represented by a class object using a
|
||||
* method invocation conversion, treating object types of primitive
|
||||
* types as if they were primitive types (that is, a Boolean actual
|
||||
* parameter type matches boolean primitive formal type). This behavior
|
||||
* is because this method is used to determine applicable methods for
|
||||
* an actual parameter list, and primitive types are represented by
|
||||
* their object duals in reflective method calls.
|
||||
*
|
||||
* @param formal the formal parameter type to which the actual
|
||||
* parameter type should be convertible
|
||||
* @param actual the actual parameter type.
|
||||
* @return true if either formal type is assignable from actual type,
|
||||
* or formal is a primitive type and actual is its corresponding object
|
||||
* type or an object type of a primitive type that can be converted to
|
||||
* the formal type.
|
||||
*/
|
||||
private static boolean isMethodInvocationConvertible(Class<?> formal, Class<?> actual) {
|
||||
// if it's a null, it means the arg was null
|
||||
if (actual == null && !formal.isPrimitive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for identity or widening reference conversion
|
||||
if (actual != null && formal.isAssignableFrom(actual)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for boxing with widening primitive conversion. Note that
|
||||
// actual parameters are never primitives.
|
||||
if (formal.isPrimitive()) {
|
||||
if (formal == Boolean.TYPE && actual == Boolean.class) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Character.TYPE && actual == Character.class) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Byte.TYPE && actual == Byte.class) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Short.TYPE && (actual == Short.class || actual == Byte.class)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Integer.TYPE && (actual == Integer.class || actual == Short.class || actual == Byte.class)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Long.TYPE
|
||||
&& (actual == Long.class
|
||||
|| actual == Integer.class
|
||||
|| actual == Short.class
|
||||
|| actual == Byte.class)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Float.TYPE
|
||||
&& (actual == Float.class
|
||||
|| actual == Long.class
|
||||
|| actual == Integer.class
|
||||
|| actual == Short.class
|
||||
|| actual == Byte.class)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Double.TYPE
|
||||
&& (actual == Double.class
|
||||
|| actual == Float.class
|
||||
|| actual == Long.class
|
||||
|| actual == Integer.class
|
||||
|| actual == Short.class
|
||||
|| actual == Byte.class)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a type represented by a class object is
|
||||
* convertible to another type represented by a class object using a
|
||||
* method invocation conversion, without matching object and primitive
|
||||
* types. This method is used to determine the more specific type when
|
||||
* comparing signatures of methods.
|
||||
*
|
||||
* @param formal the formal parameter type to which the actual
|
||||
* parameter type should be convertible
|
||||
* @param actual the actual parameter type.
|
||||
* @return true if either formal type is assignable from actual type,
|
||||
* or formal and actual are both primitive types and actual can be
|
||||
* subject to widening conversion to formal.
|
||||
*/
|
||||
private static boolean isStrictMethodInvocationConvertible(Class<?> formal, Class<?> actual) {
|
||||
// we shouldn't get a null into, but if so
|
||||
if (actual == null && !formal.isPrimitive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for identity or widening reference conversion
|
||||
if (formal.isAssignableFrom(actual)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for widening primitive conversion.
|
||||
if (formal.isPrimitive()) {
|
||||
if (formal == Short.TYPE && (actual == Byte.TYPE)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Integer.TYPE && (actual == Short.TYPE || actual == Byte.TYPE)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Long.TYPE && (actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Float.TYPE
|
||||
&& (actual == Long.TYPE || actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Double.TYPE
|
||||
&& (actual == Float.TYPE
|
||||
|| actual == Long.TYPE
|
||||
|| actual == Integer.TYPE
|
||||
|| actual == Short.TYPE
|
||||
|| actual == Byte.TYPE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* 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.internal.impl.model.reflection;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.apache.maven.api.annotations.Nonnull;
|
||||
import org.apache.maven.api.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Using simple dotted expressions to extract the values from an Object instance using JSP-like expressions
|
||||
* such as {@code project.build.sourceDirectory}.
|
||||
* <p>
|
||||
* In addition to usual getters using {@code getXxx} or {@code isXxx} suffixes, accessors
|
||||
* using {@code asXxx} or {@code toXxx} prefixes are also supported.
|
||||
*/
|
||||
public class ReflectionValueExtractor {
|
||||
private static final Object[] OBJECT_ARGS = new Object[0];
|
||||
|
||||
/**
|
||||
* Use a WeakHashMap here, so the keys (Class objects) can be garbage collected.
|
||||
* This approach prevents permgen space overflows due to retention of discarded
|
||||
* classloaders.
|
||||
*/
|
||||
private static final Map<Class<?>, WeakReference<ClassMap>> CLASS_MAPS = new WeakHashMap<>();
|
||||
|
||||
static final int EOF = -1;
|
||||
|
||||
static final char PROPERTY_START = '.';
|
||||
|
||||
static final char INDEXED_START = '[';
|
||||
|
||||
static final char INDEXED_END = ']';
|
||||
|
||||
static final char MAPPED_START = '(';
|
||||
|
||||
static final char MAPPED_END = ')';
|
||||
|
||||
static class Tokenizer {
|
||||
final String expression;
|
||||
|
||||
int idx;
|
||||
|
||||
Tokenizer(String expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public int peekChar() {
|
||||
return idx < expression.length() ? expression.charAt(idx) : EOF;
|
||||
}
|
||||
|
||||
public int skipChar() {
|
||||
return idx < expression.length() ? expression.charAt(idx++) : EOF;
|
||||
}
|
||||
|
||||
public String nextToken(char delimiter) {
|
||||
int start = idx;
|
||||
|
||||
while (idx < expression.length() && delimiter != expression.charAt(idx)) {
|
||||
idx++;
|
||||
}
|
||||
|
||||
// delimiter MUST be present
|
||||
if (idx <= start || idx >= expression.length()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return expression.substring(start, idx++);
|
||||
}
|
||||
|
||||
public String nextPropertyName() {
|
||||
final int start = idx;
|
||||
|
||||
while (idx < expression.length() && Character.isJavaIdentifierPart(expression.charAt(idx))) {
|
||||
idx++;
|
||||
}
|
||||
|
||||
// property name does not require delimiter
|
||||
if (idx <= start || idx > expression.length()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return expression.substring(start, idx);
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return idx < expression.length() ? idx : EOF;
|
||||
}
|
||||
|
||||
// to make tokenizer look pretty in debugger
|
||||
@Override
|
||||
public String toString() {
|
||||
return idx < expression.length() ? expression.substring(idx) : "<EOF>";
|
||||
}
|
||||
}
|
||||
|
||||
private ReflectionValueExtractor() {}
|
||||
|
||||
/**
|
||||
* <p>The implementation supports indexed, nested and mapped properties.</p>
|
||||
* <ul>
|
||||
* <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
|
||||
* <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
|
||||
* pattern, i.e. "user.addresses[1].street"</li>
|
||||
* <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern,
|
||||
* i.e. "user.addresses(myAddress).street"</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param expression not null expression
|
||||
* @param root not null object
|
||||
* @return the object defined by the expression
|
||||
* @throws IntrospectionException if any
|
||||
*/
|
||||
public static Object evaluate(@Nonnull String expression, @Nullable Object root) throws IntrospectionException {
|
||||
return evaluate(expression, root, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The implementation supports indexed, nested and mapped properties.
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
|
||||
* <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
|
||||
* pattern, i.e. "user.addresses[1].street"</li>
|
||||
* <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e.
|
||||
* "user.addresses(myAddress).street"</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param expression not null expression
|
||||
* @param root not null object
|
||||
* @param trimRootToken trim root token yes/no.
|
||||
* @return the object defined by the expression
|
||||
* @throws IntrospectionException if any
|
||||
*/
|
||||
public static Object evaluate(@Nonnull String expression, @Nullable Object root, boolean trimRootToken)
|
||||
throws IntrospectionException {
|
||||
Object value = root;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Walk the dots and retrieve the ultimate value desired from the
|
||||
// MavenProject instance.
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
if (expression == null || expression.isEmpty() || !Character.isJavaIdentifierStart(expression.charAt(0))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean hasDots = expression.indexOf(PROPERTY_START) >= 0;
|
||||
|
||||
final Tokenizer tokenizer;
|
||||
if (trimRootToken && hasDots) {
|
||||
tokenizer = new Tokenizer(expression);
|
||||
tokenizer.nextPropertyName();
|
||||
if (tokenizer.getPosition() == EOF) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
tokenizer = new Tokenizer("." + expression);
|
||||
}
|
||||
|
||||
int propertyPosition = tokenizer.getPosition();
|
||||
while (value != null && tokenizer.peekChar() != EOF) {
|
||||
switch (tokenizer.skipChar()) {
|
||||
case INDEXED_START:
|
||||
value = getIndexedValue(
|
||||
expression,
|
||||
propertyPosition,
|
||||
tokenizer.getPosition(),
|
||||
value,
|
||||
tokenizer.nextToken(INDEXED_END));
|
||||
break;
|
||||
case MAPPED_START:
|
||||
value = getMappedValue(
|
||||
expression,
|
||||
propertyPosition,
|
||||
tokenizer.getPosition(),
|
||||
value,
|
||||
tokenizer.nextToken(MAPPED_END));
|
||||
break;
|
||||
case PROPERTY_START:
|
||||
propertyPosition = tokenizer.getPosition();
|
||||
value = getPropertyValue(value, tokenizer.nextPropertyName());
|
||||
break;
|
||||
default:
|
||||
// could not parse expression
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Optional) {
|
||||
value = ((Optional<?>) value).orElse(null);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static Object getMappedValue(
|
||||
final String expression, final int from, final int to, final Object value, final String key)
|
||||
throws IntrospectionException {
|
||||
if (value == null || key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value instanceof Map) {
|
||||
return ((Map) value).get(key);
|
||||
}
|
||||
|
||||
final String message = String.format(
|
||||
"The token '%s' at position '%d' refers to a java.util.Map, but the value "
|
||||
+ "seems is an instance of '%s'",
|
||||
expression.subSequence(from, to), from, value.getClass());
|
||||
|
||||
throw new IntrospectionException(message);
|
||||
}
|
||||
|
||||
private static Object getIndexedValue(
|
||||
final String expression, final int from, final int to, final Object value, final String indexStr)
|
||||
throws IntrospectionException {
|
||||
try {
|
||||
int index = Integer.parseInt(indexStr);
|
||||
|
||||
if (value.getClass().isArray()) {
|
||||
return Array.get(value, index);
|
||||
}
|
||||
|
||||
if (value instanceof List) {
|
||||
return ((List) value).get(index);
|
||||
}
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String message = String.format(
|
||||
"The token '%s' at position '%d' refers to a java.util.List or an array, but the value "
|
||||
+ "seems is an instance of '%s'",
|
||||
expression.subSequence(from, to), from, value.getClass());
|
||||
|
||||
throw new IntrospectionException(message);
|
||||
}
|
||||
|
||||
private static Object getPropertyValue(Object value, String property) throws IntrospectionException {
|
||||
if (value == null || property == null || property.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClassMap classMap = getClassMap(value.getClass());
|
||||
String methodBase = Character.toTitleCase(property.charAt(0)) + property.substring(1);
|
||||
try {
|
||||
for (String prefix : Arrays.asList("get", "is", "to", "as")) {
|
||||
Method method = classMap.findMethod(prefix + methodBase);
|
||||
if (method != null) {
|
||||
return method.invoke(value, OBJECT_ARGS);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IntrospectionException(e.getTargetException());
|
||||
} catch (MethodMap.AmbiguousException | IllegalAccessException e) {
|
||||
throw new IntrospectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ClassMap getClassMap(Class<?> clazz) {
|
||||
Reference<ClassMap> ref = CLASS_MAPS.get(clazz);
|
||||
ClassMap classMap = ref != null ? ref.get() : null;
|
||||
|
||||
if (classMap == null) {
|
||||
classMap = new ClassMap(clazz);
|
||||
|
||||
CLASS_MAPS.put(clazz, new WeakReference<>(classMap));
|
||||
}
|
||||
|
||||
return classMap;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* 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.internal.impl.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.maven.api.services.InterpolatorException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class DefaultInterpolatorTest {
|
||||
|
||||
@Test
|
||||
void testBasicSubstitution() {
|
||||
Map<String, String> props = new HashMap<>();
|
||||
props.put("key0", "value0");
|
||||
props.put("key1", "${value1}");
|
||||
props.put("key2", "${value2}");
|
||||
|
||||
performSubstitution(props, Map.of("value1", "sub_value1")::get);
|
||||
|
||||
assertEquals("value0", props.get("key0"));
|
||||
assertEquals("sub_value1", props.get("key1"));
|
||||
assertEquals("", props.get("key2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBasicSubstitutionWithContext() {
|
||||
HashMap<String, String> props = new HashMap<>();
|
||||
props.put("key0", "value0");
|
||||
props.put("key1", "${value1}");
|
||||
|
||||
performSubstitution(props, Map.of("value1", "sub_value1")::get);
|
||||
|
||||
assertEquals("value0", props.get("key0"));
|
||||
assertEquals("sub_value1", props.get("key1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSubstitutionFailures() {
|
||||
assertEquals("a}", substVars("a}", "b"));
|
||||
assertEquals("${a", substVars("${a", "b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyVariable() {
|
||||
assertEquals("", substVars("${}", "b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInnerSubst() {
|
||||
assertEquals("c", substVars("${${a}}", "z", Map.of("a", "b", "b", "c")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSubstLoop() {
|
||||
assertThrows(
|
||||
InterpolatorException.class,
|
||||
() -> substVars("${a}", "a"),
|
||||
"Expected substVars() to throw an InterpolatorException, but it didn't");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoopEmpty() {
|
||||
assertEquals("${a}", substVars("${a}", null, null, null, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoopEmpty2() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("a", "${a}");
|
||||
assertEquals("${a}", substVars("${a}", null, null, null, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSubstitutionEscape() {
|
||||
assertEquals("${a}", substVars("$\\{a${#}\\}", "b"));
|
||||
assertEquals("${a}", substVars("$\\{a\\}${#}", "b"));
|
||||
assertEquals("${a}", substVars("$\\{a\\}", "b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSubstitutionOrder() {
|
||||
LinkedHashMap<String, String> map1 = new LinkedHashMap<>();
|
||||
map1.put("a", "$\\\\{var}");
|
||||
map1.put("abc", "${ab}c");
|
||||
map1.put("ab", "${a}b");
|
||||
performSubstitution(map1);
|
||||
|
||||
LinkedHashMap<String, String> map2 = new LinkedHashMap<>();
|
||||
map2.put("a", "$\\\\{var}");
|
||||
map2.put("ab", "${a}b");
|
||||
map2.put("abc", "${ab}c");
|
||||
performSubstitution(map2);
|
||||
|
||||
assertEquals(map1, map2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleEscapes() {
|
||||
LinkedHashMap<String, String> map1 = new LinkedHashMap<>();
|
||||
map1.put("a", "$\\\\{var}");
|
||||
map1.put("abc", "${ab}c");
|
||||
map1.put("ab", "${a}b");
|
||||
performSubstitution(map1);
|
||||
|
||||
assertEquals("$\\{var}", map1.get("a"));
|
||||
assertEquals("$\\{var}b", map1.get("ab"));
|
||||
assertEquals("$\\{var}bc", map1.get("abc"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreserveUnresolved() {
|
||||
Map<String, String> props = new HashMap<>();
|
||||
props.put("a", "${b}");
|
||||
assertEquals("", substVars("${b}", "a", props, null, true));
|
||||
assertEquals("${b}", substVars("${b}", "a", props, null, false));
|
||||
|
||||
props.put("b", "c");
|
||||
assertEquals("c", substVars("${b}", "a", props, null, true));
|
||||
assertEquals("c", substVars("${b}", "a", props, null, false));
|
||||
|
||||
props.put("c", "${d}${d}");
|
||||
assertEquals("${d}${d}", substVars("${d}${d}", "c", props, null, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExpansion() {
|
||||
Map<String, String> props = new LinkedHashMap<>();
|
||||
props.put("a", "foo");
|
||||
props.put("b", "");
|
||||
|
||||
props.put("a_cm", "${a:-bar}");
|
||||
props.put("b_cm", "${b:-bar}");
|
||||
props.put("c_cm", "${c:-bar}");
|
||||
|
||||
props.put("a_cp", "${a:+bar}");
|
||||
props.put("b_cp", "${b:+bar}");
|
||||
props.put("c_cp", "${c:+bar}");
|
||||
|
||||
performSubstitution(props);
|
||||
|
||||
assertEquals("foo", props.get("a_cm"));
|
||||
assertEquals("bar", props.get("b_cm"));
|
||||
assertEquals("bar", props.get("c_cm"));
|
||||
|
||||
assertEquals("bar", props.get("a_cp"));
|
||||
assertEquals("", props.get("b_cp"));
|
||||
assertEquals("", props.get("c_cp"));
|
||||
}
|
||||
|
||||
private void performSubstitution(Map<String, String> props) {
|
||||
performSubstitution(props, null);
|
||||
}
|
||||
|
||||
private void performSubstitution(Map<String, String> props, Function<String, String> callback) {
|
||||
new DefaultInterpolator().performSubstitution(props, callback);
|
||||
}
|
||||
|
||||
private String substVars(
|
||||
String val,
|
||||
String currentKey,
|
||||
Map<String, String> configProps,
|
||||
Function<String, String> callback,
|
||||
boolean defaultsToEmptyString) {
|
||||
return new DefaultInterpolator()
|
||||
.substVars(val, currentKey, null, configProps, callback, null, defaultsToEmptyString);
|
||||
}
|
||||
|
||||
private String substVars(String val, String currentKey) {
|
||||
return new DefaultInterpolator().substVars(val, currentKey, null, null, null, null, true);
|
||||
}
|
||||
|
||||
private String substVars(String val, String currentKey, Map<String, String> configProps) {
|
||||
return new DefaultInterpolator().substVars(val, currentKey, null, configProps);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,574 @@
|
|||
/*
|
||||
* 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.internal.impl.model.reflection;
|
||||
|
||||
/*
|
||||
* Copyright The 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.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/**
|
||||
* ReflectionValueExtractorTest class.
|
||||
*/
|
||||
public class ReflectionValueExtractorTest {
|
||||
private Project project;
|
||||
|
||||
/**
|
||||
* <p>setUp.</p>
|
||||
*/
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Dependency dependency1 = new Dependency();
|
||||
dependency1.setArtifactId("dep1");
|
||||
Dependency dependency2 = new Dependency();
|
||||
dependency2.setArtifactId("dep2");
|
||||
|
||||
project = new Project();
|
||||
project.setModelVersion("4.0.0");
|
||||
project.setGroupId("org.apache.maven");
|
||||
project.setArtifactId("maven-core");
|
||||
project.setName("Maven");
|
||||
project.setVersion("2.0-SNAPSHOT");
|
||||
project.setScm(new Scm());
|
||||
project.getScm().setConnection("scm-connection");
|
||||
project.addDependency(dependency1);
|
||||
project.addDependency(dependency2);
|
||||
project.setBuild(new Build());
|
||||
|
||||
// Build up an artifactMap
|
||||
project.addArtifact(new Artifact("g0", "a0", "v0", "e0", "c0"));
|
||||
project.addArtifact(new Artifact("g1", "a1", "v1", "e1", "c1"));
|
||||
project.addArtifact(new Artifact("g2", "a2", "v2", "e2", "c2"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testValueExtraction.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
void testValueExtraction() throws Exception {
|
||||
// ----------------------------------------------------------------------
|
||||
// Top level values
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
assertEquals("4.0.0", ReflectionValueExtractor.evaluate("project.modelVersion", project));
|
||||
|
||||
assertEquals("org.apache.maven", ReflectionValueExtractor.evaluate("project.groupId", project));
|
||||
|
||||
assertEquals("maven-core", ReflectionValueExtractor.evaluate("project.artifactId", project));
|
||||
|
||||
assertEquals("Maven", ReflectionValueExtractor.evaluate("project.name", project));
|
||||
|
||||
assertEquals("2.0-SNAPSHOT", ReflectionValueExtractor.evaluate("project.version", project));
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SCM
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
assertEquals("scm-connection", ReflectionValueExtractor.evaluate("project.scm.connection", project));
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Dependencies
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
List<?> dependencies = (List) ReflectionValueExtractor.evaluate("project.dependencies", project);
|
||||
|
||||
assertNotNull(dependencies);
|
||||
|
||||
assertEquals(2, dependencies.size());
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Dependencies - using index notation
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// List
|
||||
Dependency dependency = (Dependency) ReflectionValueExtractor.evaluate("project.dependencies[0]", project);
|
||||
|
||||
assertNotNull(dependency);
|
||||
|
||||
assertEquals("dep1", dependency.getArtifactId());
|
||||
|
||||
String artifactId = (String) ReflectionValueExtractor.evaluate("project.dependencies[1].artifactId", project);
|
||||
|
||||
assertEquals("dep2", artifactId);
|
||||
|
||||
// Array
|
||||
|
||||
dependency = (Dependency) ReflectionValueExtractor.evaluate("project.dependenciesAsArray[0]", project);
|
||||
|
||||
assertNotNull(dependency);
|
||||
|
||||
assertEquals("dep1", dependency.getArtifactId());
|
||||
|
||||
artifactId = (String) ReflectionValueExtractor.evaluate("project.dependenciesAsArray[1].artifactId", project);
|
||||
|
||||
assertEquals("dep2", artifactId);
|
||||
|
||||
// Map
|
||||
|
||||
dependency = (Dependency) ReflectionValueExtractor.evaluate("project.dependenciesAsMap(dep1)", project);
|
||||
|
||||
assertNotNull(dependency);
|
||||
|
||||
assertEquals("dep1", dependency.getArtifactId());
|
||||
|
||||
artifactId = (String) ReflectionValueExtractor.evaluate("project.dependenciesAsMap(dep2).artifactId", project);
|
||||
|
||||
assertEquals("dep2", artifactId);
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Build
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Build build = (Build) ReflectionValueExtractor.evaluate("project.build", project);
|
||||
|
||||
assertNotNull(build);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testValueExtractorWithAInvalidExpression.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testValueExtractorWithAInvalidExpression() throws Exception {
|
||||
assertNull(ReflectionValueExtractor.evaluate("project.foo", project));
|
||||
assertNull(ReflectionValueExtractor.evaluate("project.dependencies[10]", project));
|
||||
assertNull(ReflectionValueExtractor.evaluate("project.dependencies[0].foo", project));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testMappedDottedKey.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testMappedDottedKey() throws Exception {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("a.b", "a.b-value");
|
||||
|
||||
assertEquals("a.b-value", ReflectionValueExtractor.evaluate("h.value(a.b)", new ValueHolder(map)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testIndexedMapped.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testIndexedMapped() throws Exception {
|
||||
Map<Object, Object> map = new HashMap<Object, Object>();
|
||||
map.put("a", "a-value");
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
list.add(map);
|
||||
|
||||
assertEquals("a-value", ReflectionValueExtractor.evaluate("h.value[0](a)", new ValueHolder(list)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testMappedIndexed.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testMappedIndexed() throws Exception {
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
list.add("a-value");
|
||||
Map<Object, Object> map = new HashMap<Object, Object>();
|
||||
map.put("a", list);
|
||||
assertEquals("a-value", ReflectionValueExtractor.evaluate("h.value(a)[0]", new ValueHolder(map)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testMappedMissingDot.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testMappedMissingDot() throws Exception {
|
||||
Map<Object, Object> map = new HashMap<Object, Object>();
|
||||
map.put("a", new ValueHolder("a-value"));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value(a)value", new ValueHolder(map)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testIndexedMissingDot.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testIndexedMissingDot() throws Exception {
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
list.add(new ValueHolder("a-value"));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[0]value", new ValueHolder(list)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testDotDot.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testDotDot() throws Exception {
|
||||
assertNull(ReflectionValueExtractor.evaluate("h..value", new ValueHolder("value")));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testBadIndexedSyntax.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testBadIndexedSyntax() throws Exception {
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
list.add("a-value");
|
||||
Object value = new ValueHolder(list);
|
||||
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[]", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[a]", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[0", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[0)", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[-1]", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testBadMappedSyntax.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testBadMappedSyntax() throws Exception {
|
||||
Map<Object, Object> map = new HashMap<Object, Object>();
|
||||
map.put("a", "a-value");
|
||||
Object value = new ValueHolder(map);
|
||||
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value(", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value()", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value(a", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value(a]", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testIllegalIndexedType.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testIllegalIndexedType() throws Exception {
|
||||
try {
|
||||
ReflectionValueExtractor.evaluate("h.value[1]", new ValueHolder("string"));
|
||||
} catch (Exception e) {
|
||||
// TODO assert exception message
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testIllegalMappedType.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testIllegalMappedType() throws Exception {
|
||||
try {
|
||||
ReflectionValueExtractor.evaluate("h.value(key)", new ValueHolder("string"));
|
||||
} catch (Exception e) {
|
||||
// TODO assert exception message
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testTrimRootToken.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testTrimRootToken() throws Exception {
|
||||
assertNull(ReflectionValueExtractor.evaluate("project", project, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testArtifactMap.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testArtifactMap() throws Exception {
|
||||
assertEquals(
|
||||
"g0",
|
||||
((Artifact) ReflectionValueExtractor.evaluate("project.artifactMap(g0:a0:c0)", project)).getGroupId());
|
||||
assertEquals(
|
||||
"a1",
|
||||
((Artifact) ReflectionValueExtractor.evaluate("project.artifactMap(g1:a1:c1)", project))
|
||||
.getArtifactId());
|
||||
assertEquals(
|
||||
"c2",
|
||||
((Artifact) ReflectionValueExtractor.evaluate("project.artifactMap(g2:a2:c2)", project))
|
||||
.getClassifier());
|
||||
}
|
||||
|
||||
public static class Artifact {
|
||||
private String groupId;
|
||||
|
||||
private String artifactId;
|
||||
|
||||
private String version;
|
||||
|
||||
private String extension;
|
||||
|
||||
private String classifier;
|
||||
|
||||
public Artifact(String groupId, String artifactId, String version, String extension, String classifier) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
this.extension = extension;
|
||||
this.classifier = classifier;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
}
|
||||
|
||||
public void setArtifactId(String artifactId) {
|
||||
this.artifactId = artifactId;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
return extension;
|
||||
}
|
||||
|
||||
public void setExtension(String extension) {
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
public String getClassifier() {
|
||||
return classifier;
|
||||
}
|
||||
|
||||
public void setClassifier(String classifier) {
|
||||
this.classifier = classifier;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Project {
|
||||
private String modelVersion;
|
||||
|
||||
private String groupId;
|
||||
|
||||
private Scm scm;
|
||||
|
||||
private List<Dependency> dependencies = new ArrayList<>();
|
||||
|
||||
private Build build;
|
||||
|
||||
private String artifactId;
|
||||
|
||||
private String name;
|
||||
|
||||
private String version;
|
||||
|
||||
private Map<String, Artifact> artifactMap = new HashMap<>();
|
||||
private String description;
|
||||
|
||||
public void setModelVersion(String modelVersion) {
|
||||
this.modelVersion = modelVersion;
|
||||
}
|
||||
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public void setScm(Scm scm) {
|
||||
this.scm = scm;
|
||||
}
|
||||
|
||||
public void addDependency(Dependency dependency) {
|
||||
this.dependencies.add(dependency);
|
||||
}
|
||||
|
||||
public void setBuild(Build build) {
|
||||
this.build = build;
|
||||
}
|
||||
|
||||
public void setArtifactId(String artifactId) {
|
||||
this.artifactId = artifactId;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Scm getScm() {
|
||||
return scm;
|
||||
}
|
||||
|
||||
public String getModelVersion() {
|
||||
return modelVersion;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public List<Dependency> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
public Build getBuild() {
|
||||
return build;
|
||||
}
|
||||
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Dependency[] getDependenciesAsArray() {
|
||||
return getDependencies().toArray(new Dependency[0]);
|
||||
}
|
||||
|
||||
public Map<String, Dependency> getDependenciesAsMap() {
|
||||
Map<String, Dependency> ret = new HashMap<>();
|
||||
for (Dependency dep : getDependencies()) {
|
||||
ret.put(dep.getArtifactId(), dep);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ${project.artifactMap(g:a:v)}
|
||||
public void addArtifact(Artifact a) {
|
||||
artifactMap.put(a.getGroupId() + ":" + a.getArtifactId() + ":" + a.getClassifier(), a);
|
||||
}
|
||||
|
||||
public Map<String, Artifact> getArtifactMap() {
|
||||
return artifactMap;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Build {}
|
||||
|
||||
public static class Dependency {
|
||||
private String artifactId;
|
||||
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
}
|
||||
|
||||
public void setArtifactId(String id) {
|
||||
artifactId = id;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Scm {
|
||||
private String connection;
|
||||
|
||||
public void setConnection(String connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public String getConnection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ValueHolder {
|
||||
private final Object value;
|
||||
|
||||
public ValueHolder(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testRootPropertyRegression.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testRootPropertyRegression() throws Exception {
|
||||
Project project = new Project();
|
||||
project.setDescription("c:\\\\org\\apache\\test");
|
||||
Object evalued = ReflectionValueExtractor.evaluate("description", project);
|
||||
assertNotNull(evalued);
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ import org.apache.maven.internal.impl.DefaultUrlNormalizer;
|
|||
import org.apache.maven.internal.impl.model.DefaultDependencyManagementImporter;
|
||||
import org.apache.maven.internal.impl.model.DefaultDependencyManagementInjector;
|
||||
import org.apache.maven.internal.impl.model.DefaultInheritanceAssembler;
|
||||
import org.apache.maven.internal.impl.model.DefaultInterpolator;
|
||||
import org.apache.maven.internal.impl.model.DefaultModelBuilder;
|
||||
import org.apache.maven.internal.impl.model.DefaultModelCacheFactory;
|
||||
import org.apache.maven.internal.impl.model.DefaultModelInterpolator;
|
||||
|
@ -1043,7 +1044,10 @@ public class RepositorySystemSupplier implements Supplier<RepositorySystem> {
|
|||
new DefaultModelValidator(),
|
||||
new DefaultModelNormalizer(),
|
||||
new DefaultModelInterpolator(
|
||||
new DefaultPathTranslator(), new DefaultUrlNormalizer(), new DefaultRootLocator()),
|
||||
new DefaultPathTranslator(),
|
||||
new DefaultUrlNormalizer(),
|
||||
new DefaultRootLocator(),
|
||||
new DefaultInterpolator()),
|
||||
new DefaultModelPathTranslator(new DefaultPathTranslator()),
|
||||
new DefaultModelUrlNormalizer(new DefaultUrlNormalizer()),
|
||||
new DefaultSuperPomProvider(modelProcessor),
|
||||
|
@ -1054,11 +1058,13 @@ public class RepositorySystemSupplier implements Supplier<RepositorySystem> {
|
|||
new DefaultDependencyManagementInjector(),
|
||||
new DefaultDependencyManagementImporter(),
|
||||
new DefaultPluginConfigurationExpander(),
|
||||
new ProfileActivationFilePathInterpolator(new DefaultPathTranslator(), new DefaultRootLocator()),
|
||||
new ProfileActivationFilePathInterpolator(
|
||||
new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()),
|
||||
new DefaultModelVersionParser(getVersionScheme()),
|
||||
List.of(),
|
||||
new DefaultModelCacheFactory(),
|
||||
new DefaultModelResolver());
|
||||
new DefaultModelResolver(),
|
||||
new DefaultInterpolator());
|
||||
}
|
||||
|
||||
private RepositorySystem repositorySystem;
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.apache.maven.api.model.Model;
|
|||
import org.apache.maven.api.model.ModelBase;
|
||||
import org.apache.maven.api.model.Profile;
|
||||
import org.apache.maven.api.model.Repository;
|
||||
import org.apache.maven.api.services.Interpolator;
|
||||
import org.apache.maven.api.services.ModelBuilder;
|
||||
import org.apache.maven.api.services.ModelBuilderException;
|
||||
import org.apache.maven.api.services.ModelBuilderRequest;
|
||||
|
@ -96,6 +97,7 @@ class DefaultConsumerPomBuilder implements ConsumerPomBuilder {
|
|||
private final List<ModelTransformer> transformers;
|
||||
private final ModelCacheFactory modelCacheFactory;
|
||||
private final ModelResolver modelResolver;
|
||||
private final Interpolator interpolator;
|
||||
|
||||
@Inject
|
||||
@SuppressWarnings("checkstyle:ParameterNumber")
|
||||
|
@ -118,7 +120,8 @@ class DefaultConsumerPomBuilder implements ConsumerPomBuilder {
|
|||
ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator,
|
||||
List<ModelTransformer> transformers,
|
||||
ModelCacheFactory modelCacheFactory,
|
||||
ModelResolver modelResolver) {
|
||||
ModelResolver modelResolver,
|
||||
Interpolator interpolator) {
|
||||
this.profileInjector = profileInjector;
|
||||
this.inheritanceAssembler = inheritanceAssembler;
|
||||
this.dependencyManagementImporter = dependencyManagementImporter;
|
||||
|
@ -138,6 +141,7 @@ class DefaultConsumerPomBuilder implements ConsumerPomBuilder {
|
|||
this.transformers = transformers;
|
||||
this.modelCacheFactory = modelCacheFactory;
|
||||
this.modelResolver = modelResolver;
|
||||
this.interpolator = interpolator;
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
@ -197,7 +201,8 @@ class DefaultConsumerPomBuilder implements ConsumerPomBuilder {
|
|||
versionParser,
|
||||
transformers,
|
||||
modelCacheFactory,
|
||||
modelResolver);
|
||||
modelResolver,
|
||||
interpolator);
|
||||
InternalSession iSession = InternalSession.from(session);
|
||||
ModelBuilderRequest.ModelBuilderRequestBuilder request = ModelBuilderRequest.builder();
|
||||
request.requestType(ModelBuilderRequest.RequestType.BUILD_POM);
|
||||
|
|
|
@ -23,7 +23,7 @@ import java.nio.file.Path;
|
|||
import java.util.Properties;
|
||||
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.model.interpolation.reflection.ReflectionValueExtractor;
|
||||
import org.apache.maven.internal.impl.model.reflection.ReflectionValueExtractor;
|
||||
import org.apache.maven.plugin.descriptor.MojoDescriptor;
|
||||
import org.apache.maven.plugin.descriptor.PluginDescriptor;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
|
|
@ -27,7 +27,7 @@ import java.util.Map;
|
|||
import org.apache.maven.api.MojoExecution;
|
||||
import org.apache.maven.api.Project;
|
||||
import org.apache.maven.api.Session;
|
||||
import org.apache.maven.model.interpolation.reflection.ReflectionValueExtractor;
|
||||
import org.apache.maven.internal.impl.model.reflection.ReflectionValueExtractor;
|
||||
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
|
||||
import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.apache.maven.execution.MavenSession;
|
|||
import org.apache.maven.internal.impl.DefaultRepositoryFactory;
|
||||
import org.apache.maven.internal.impl.DefaultSession;
|
||||
import org.apache.maven.internal.impl.InternalSession;
|
||||
import org.apache.maven.internal.impl.model.DefaultInterpolator;
|
||||
import org.codehaus.plexus.PlexusContainer;
|
||||
import org.codehaus.plexus.testing.PlexusTest;
|
||||
import org.eclipse.aether.DefaultRepositorySystemSession;
|
||||
|
@ -82,8 +83,10 @@ public abstract class AbstractRepositoryTestCase {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
new SimpleLookup(List.of(new DefaultRepositoryFactory(new DefaultRemoteRepositoryManager(
|
||||
new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider())))),
|
||||
new SimpleLookup(List.of(
|
||||
new DefaultRepositoryFactory(new DefaultRemoteRepositoryManager(
|
||||
new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider())),
|
||||
new DefaultInterpolator())),
|
||||
null);
|
||||
InternalSession.associate(rsession, session);
|
||||
|
||||
|
|
|
@ -41,11 +41,11 @@ import org.apache.maven.execution.DefaultMavenExecutionRequest;
|
|||
import org.apache.maven.execution.DefaultMavenExecutionResult;
|
||||
import org.apache.maven.execution.MavenExecutionRequest;
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.internal.impl.model.reflection.IntrospectionException;
|
||||
import org.apache.maven.model.Build;
|
||||
import org.apache.maven.model.Dependency;
|
||||
import org.apache.maven.model.Model;
|
||||
import org.apache.maven.model.building.DefaultModelBuildingRequest;
|
||||
import org.apache.maven.model.interpolation.reflection.IntrospectionException;
|
||||
import org.apache.maven.model.root.RootLocator;
|
||||
import org.apache.maven.plugin.descriptor.MojoDescriptor;
|
||||
import org.apache.maven.plugin.descriptor.PluginDescriptor;
|
||||
|
|
|
@ -48,10 +48,10 @@ import org.apache.maven.internal.impl.DefaultMojoExecution;
|
|||
import org.apache.maven.internal.impl.DefaultProject;
|
||||
import org.apache.maven.internal.impl.DefaultSession;
|
||||
import org.apache.maven.internal.impl.InternalMavenSession;
|
||||
import org.apache.maven.internal.impl.model.reflection.IntrospectionException;
|
||||
import org.apache.maven.model.Build;
|
||||
import org.apache.maven.model.Model;
|
||||
import org.apache.maven.model.building.DefaultModelBuildingRequest;
|
||||
import org.apache.maven.model.interpolation.reflection.IntrospectionException;
|
||||
import org.apache.maven.model.root.RootLocator;
|
||||
import org.apache.maven.plugin.descriptor.MojoDescriptor;
|
||||
import org.apache.maven.plugin.descriptor.PluginDescriptor;
|
||||
|
|
|
@ -18,29 +18,29 @@
|
|||
*/
|
||||
package org.apache.maven.cli;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.name.Names;
|
||||
import org.apache.maven.api.services.Interpolator;
|
||||
import org.apache.maven.api.xml.XmlNode;
|
||||
import org.apache.maven.extension.internal.CoreExtensionEntry;
|
||||
import org.apache.maven.internal.impl.model.DefaultInterpolator;
|
||||
import org.apache.maven.internal.xml.XmlNodeImpl;
|
||||
import org.apache.maven.internal.xml.XmlPlexusConfiguration;
|
||||
import org.apache.maven.model.v4.MavenTransformer;
|
||||
import org.codehaus.plexus.configuration.PlexusConfiguration;
|
||||
import org.codehaus.plexus.interpolation.InterpolationException;
|
||||
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
|
||||
import org.codehaus.plexus.interpolation.ValueSource;
|
||||
|
||||
public class ExtensionConfigurationModule implements Module {
|
||||
|
||||
private final CoreExtensionEntry extension;
|
||||
private final Iterable<ValueSource> valueSources;
|
||||
private final Function<String, String> callback;
|
||||
private final DefaultInterpolator interpolator = new DefaultInterpolator();
|
||||
|
||||
public ExtensionConfigurationModule(CoreExtensionEntry extension, ValueSource... valueSources) {
|
||||
public ExtensionConfigurationModule(CoreExtensionEntry extension, Function<String, String> callback) {
|
||||
this.extension = extension;
|
||||
this.valueSources = Arrays.asList(valueSources);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -50,7 +50,9 @@ public class ExtensionConfigurationModule implements Module {
|
|||
if (configuration == null) {
|
||||
configuration = new XmlNodeImpl("configuration");
|
||||
}
|
||||
configuration = new Interpolator().transform(configuration);
|
||||
Function<String, String> cb = Interpolator.memoize(callback);
|
||||
Function<String, String> it = s -> interpolator.interpolate(s, cb);
|
||||
configuration = new ExtensionInterpolator(it).transform(configuration);
|
||||
|
||||
binder.bind(XmlNode.class)
|
||||
.annotatedWith(Names.named(extension.getKey()))
|
||||
|
@ -61,26 +63,13 @@ public class ExtensionConfigurationModule implements Module {
|
|||
}
|
||||
}
|
||||
|
||||
class Interpolator extends MavenTransformer {
|
||||
final StringSearchInterpolator interpolator;
|
||||
|
||||
Interpolator() {
|
||||
super(null);
|
||||
interpolator = new StringSearchInterpolator();
|
||||
interpolator.setCacheAnswers(true);
|
||||
valueSources.forEach(interpolator::addValueSource);
|
||||
static class ExtensionInterpolator extends MavenTransformer {
|
||||
ExtensionInterpolator(Function<String, String> transformer) {
|
||||
super(transformer);
|
||||
}
|
||||
|
||||
public XmlNode transform(XmlNode node) {
|
||||
return super.transform(node);
|
||||
}
|
||||
|
||||
protected String transform(String str) {
|
||||
try {
|
||||
return interpolator.interpolate(str);
|
||||
} catch (InterpolationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,9 +120,6 @@ import org.codehaus.plexus.classworlds.ClassWorld;
|
|||
import org.codehaus.plexus.classworlds.realm.ClassRealm;
|
||||
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
|
||||
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
|
||||
import org.codehaus.plexus.interpolation.AbstractValueSource;
|
||||
import org.codehaus.plexus.interpolation.BasicInterpolator;
|
||||
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
|
||||
import org.codehaus.plexus.logging.LoggerManager;
|
||||
import org.eclipse.aether.DefaultRepositoryCache;
|
||||
import org.eclipse.aether.transfer.TransferListener;
|
||||
|
@ -646,21 +643,29 @@ public class MavenCli {
|
|||
populateProperties(cliRequest.commandLine, paths, cliRequest.systemProperties, cliRequest.userProperties);
|
||||
|
||||
// now that we have properties, interpolate all arguments
|
||||
BasicInterpolator interpolator =
|
||||
createInterpolator(paths, cliRequest.systemProperties, cliRequest.userProperties);
|
||||
Function<String, String> callback = v -> {
|
||||
String r = paths.getProperty(v);
|
||||
if (r == null) {
|
||||
r = cliRequest.systemProperties.getProperty(v);
|
||||
}
|
||||
if (r == null) {
|
||||
r = cliRequest.userProperties.getProperty(v);
|
||||
}
|
||||
return r != null ? r : v;
|
||||
};
|
||||
CommandLine.Builder commandLineBuilder = new CommandLine.Builder();
|
||||
commandLineBuilder.setDeprecatedHandler(o -> {});
|
||||
for (Option option : cliRequest.commandLine.getOptions()) {
|
||||
if (!String.valueOf(CLIManager.SET_USER_PROPERTY).equals(option.getOpt())) {
|
||||
List<String> values = option.getValuesList();
|
||||
for (ListIterator<String> it = values.listIterator(); it.hasNext(); ) {
|
||||
it.set(interpolator.interpolate(it.next()));
|
||||
it.set(MavenPropertiesLoader.substVars(it.next(), null, null, callback));
|
||||
}
|
||||
}
|
||||
commandLineBuilder.addOption(option);
|
||||
}
|
||||
for (String arg : cliRequest.commandLine.getArgList()) {
|
||||
commandLineBuilder.addArg(interpolator.interpolate(arg));
|
||||
commandLineBuilder.addArg(MavenPropertiesLoader.substVars(arg, null, null, callback));
|
||||
}
|
||||
cliRequest.commandLine = commandLineBuilder.build();
|
||||
}
|
||||
|
@ -719,15 +724,12 @@ public class MavenCli {
|
|||
|
||||
container.setLoggerManager(plexusLoggerManager);
|
||||
|
||||
AbstractValueSource extensionSource = new AbstractValueSource(false) {
|
||||
@Override
|
||||
public Object getValue(String expression) {
|
||||
Object value = cliRequest.userProperties.getProperty(expression);
|
||||
if (value == null) {
|
||||
value = cliRequest.systemProperties.getProperty(expression);
|
||||
}
|
||||
return value;
|
||||
Function<String, String> extensionSource = expression -> {
|
||||
String value = cliRequest.userProperties.getProperty(expression);
|
||||
if (value == null) {
|
||||
value = cliRequest.systemProperties.getProperty(expression);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
for (CoreExtensionEntry extension : extensions) {
|
||||
container.discoverComponents(
|
||||
|
@ -1744,23 +1746,6 @@ public class MavenCli {
|
|||
};
|
||||
}
|
||||
|
||||
private static BasicInterpolator createInterpolator(Properties... properties) {
|
||||
StringSearchInterpolator interpolator = new StringSearchInterpolator();
|
||||
interpolator.addValueSource(new AbstractValueSource(false) {
|
||||
@Override
|
||||
public Object getValue(String expression) {
|
||||
for (Properties props : properties) {
|
||||
Object val = props.getProperty(expression);
|
||||
if (val != null) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return interpolator;
|
||||
}
|
||||
|
||||
private static String stripLeadingAndTrailingQuotes(String str) {
|
||||
final int length = str.length();
|
||||
if (length > 1
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.maven.RepositoryUtils;
|
||||
|
@ -36,6 +37,8 @@ import org.apache.maven.api.model.Plugin;
|
|||
import org.apache.maven.api.services.ArtifactCoordinatesFactory;
|
||||
import org.apache.maven.api.services.ArtifactManager;
|
||||
import org.apache.maven.api.services.ArtifactResolver;
|
||||
import org.apache.maven.api.services.Interpolator;
|
||||
import org.apache.maven.api.services.InterpolatorException;
|
||||
import org.apache.maven.api.services.RepositoryFactory;
|
||||
import org.apache.maven.api.services.VersionParser;
|
||||
import org.apache.maven.api.services.VersionRangeResolver;
|
||||
|
@ -54,6 +57,7 @@ import org.apache.maven.internal.impl.DefaultSession;
|
|||
import org.apache.maven.internal.impl.DefaultVersionParser;
|
||||
import org.apache.maven.internal.impl.DefaultVersionRangeResolver;
|
||||
import org.apache.maven.internal.impl.InternalSession;
|
||||
import org.apache.maven.internal.impl.model.DefaultInterpolator;
|
||||
import org.apache.maven.plugin.PluginResolutionException;
|
||||
import org.apache.maven.plugin.internal.DefaultPluginDependenciesResolver;
|
||||
import org.apache.maven.resolver.MavenChainedWorkspaceReader;
|
||||
|
@ -62,10 +66,6 @@ import org.codehaus.plexus.DefaultPlexusContainer;
|
|||
import org.codehaus.plexus.PlexusContainer;
|
||||
import org.codehaus.plexus.classworlds.ClassWorld;
|
||||
import org.codehaus.plexus.classworlds.realm.ClassRealm;
|
||||
import org.codehaus.plexus.interpolation.InterpolationException;
|
||||
import org.codehaus.plexus.interpolation.Interpolator;
|
||||
import org.codehaus.plexus.interpolation.MapBasedValueSource;
|
||||
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
|
||||
import org.eclipse.aether.RepositorySystem;
|
||||
import org.eclipse.aether.RepositorySystemSession;
|
||||
import org.eclipse.aether.RepositorySystemSession.CloseableSession;
|
||||
|
@ -138,7 +138,7 @@ public class BootstrapCoreExtensionManager {
|
|||
InternalSession.associate(repoSession, iSession);
|
||||
|
||||
List<RemoteRepository> repositories = RepositoryUtils.toRepos(request.getPluginArtifactRepositories());
|
||||
Interpolator interpolator = createInterpolator(request);
|
||||
Function<String, String> interpolator = createInterpolator(request);
|
||||
|
||||
return resolveCoreExtensions(repoSession, repositories, providedArtifacts, extensions, interpolator);
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ public class BootstrapCoreExtensionManager {
|
|||
List<RemoteRepository> repositories,
|
||||
Set<String> providedArtifacts,
|
||||
List<CoreExtension> configuration,
|
||||
Interpolator interpolator)
|
||||
Function<String, String> interpolator)
|
||||
throws Exception {
|
||||
List<CoreExtensionEntry> extensions = new ArrayList<>();
|
||||
|
||||
|
@ -207,7 +207,7 @@ public class BootstrapCoreExtensionManager {
|
|||
RepositorySystemSession repoSession,
|
||||
List<RemoteRepository> repositories,
|
||||
DependencyFilter dependencyFilter,
|
||||
Interpolator interpolator)
|
||||
Function<String, String> interpolator)
|
||||
throws ExtensionResolutionException {
|
||||
try {
|
||||
/* TODO: Enhance the PluginDependenciesResolver to provide a
|
||||
|
@ -215,9 +215,9 @@ public class BootstrapCoreExtensionManager {
|
|||
* object instead of a Plugin as this makes no sense.
|
||||
*/
|
||||
Plugin plugin = Plugin.newBuilder()
|
||||
.groupId(interpolator.interpolate(extension.getGroupId()))
|
||||
.artifactId(interpolator.interpolate(extension.getArtifactId()))
|
||||
.version(interpolator.interpolate(extension.getVersion()))
|
||||
.groupId(interpolator.apply(extension.getGroupId()))
|
||||
.artifactId(interpolator.apply(extension.getArtifactId()))
|
||||
.version(interpolator.apply(extension.getVersion()))
|
||||
.build();
|
||||
|
||||
DependencyResult result = pluginDependenciesResolver.resolveCoreExtension(
|
||||
|
@ -226,16 +226,21 @@ public class BootstrapCoreExtensionManager {
|
|||
.filter(ArtifactResult::isResolved)
|
||||
.map(ArtifactResult::getArtifact)
|
||||
.collect(Collectors.toList());
|
||||
} catch (PluginResolutionException | InterpolationException e) {
|
||||
} catch (PluginResolutionException | InterpolatorException e) {
|
||||
throw new ExtensionResolutionException(extension, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Interpolator createInterpolator(MavenExecutionRequest request) {
|
||||
StringSearchInterpolator interpolator = new StringSearchInterpolator();
|
||||
interpolator.addValueSource(new MapBasedValueSource(request.getUserProperties()));
|
||||
interpolator.addValueSource(new MapBasedValueSource(request.getSystemProperties()));
|
||||
return interpolator;
|
||||
private static Function<String, String> createInterpolator(MavenExecutionRequest request) {
|
||||
Interpolator interpolator = new DefaultInterpolator();
|
||||
Function<String, String> callback = v -> {
|
||||
String r = request.getUserProperties().getProperty(v);
|
||||
if (r == null) {
|
||||
r = request.getSystemProperties().getProperty(v);
|
||||
}
|
||||
return r != null ? r : v;
|
||||
};
|
||||
return v -> interpolator.interpolate(v, callback);
|
||||
}
|
||||
|
||||
static class SimpleSession extends DefaultSession {
|
||||
|
@ -267,6 +272,8 @@ public class BootstrapCoreExtensionManager {
|
|||
} else if (clazz == RepositoryFactory.class) {
|
||||
return (T) new DefaultRepositoryFactory(new DefaultRemoteRepositoryManager(
|
||||
new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider()));
|
||||
} else if (clazz == Interpolator.class) {
|
||||
return (T) new DefaultInterpolator();
|
||||
// } else if (clazz == ModelResolver.class) {
|
||||
// return (T) new DefaultModelResolver();
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.maven.internal.impl.model.DefaultInterpolator;
|
||||
|
||||
/**
|
||||
* Enhancement of the standard <code>Properties</code>
|
||||
* managing the maintain of comments, etc.
|
||||
|
@ -472,7 +474,7 @@ public class MavenProperties extends AbstractMap<String, String> {
|
|||
}
|
||||
|
||||
public void substitute(Function<String, String> callback) {
|
||||
InterpolationHelper.performSubstitution(storage, callback);
|
||||
new DefaultInterpolator().interpolate(storage, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,10 +22,11 @@ import java.io.IOException;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.apache.maven.cli.props.InterpolationHelper.substVars;
|
||||
import org.apache.maven.internal.impl.model.DefaultInterpolator;
|
||||
|
||||
public class MavenPropertiesLoader {
|
||||
|
||||
|
@ -42,7 +43,7 @@ public class MavenPropertiesLoader {
|
|||
sp.load(path);
|
||||
}
|
||||
properties.forEach(
|
||||
(k, v) -> sp.put(k.toString(), escape ? InterpolationHelper.escape(v.toString()) : v.toString()));
|
||||
(k, v) -> sp.put(k.toString(), escape ? DefaultInterpolator.escape(v.toString()) : v.toString()));
|
||||
loadIncludes(path, sp, callback);
|
||||
substitute(sp, callback);
|
||||
sp.forEach(properties::setProperty);
|
||||
|
@ -57,9 +58,9 @@ public class MavenPropertiesLoader {
|
|||
}
|
||||
if (name.startsWith(OVERRIDE_PREFIX)) {
|
||||
String overrideName = name.substring(OVERRIDE_PREFIX.length());
|
||||
props.put(overrideName, substVars(value, name, null, props, callback));
|
||||
props.put(overrideName, substVars(value, name, props, callback));
|
||||
} else {
|
||||
props.put(name, substVars(value, name, null, props, callback));
|
||||
props.put(name, substVars(value, name, props, callback));
|
||||
}
|
||||
}
|
||||
props.keySet().removeIf(k -> k.startsWith(OVERRIDE_PREFIX));
|
||||
|
@ -80,7 +81,7 @@ public class MavenPropertiesLoader {
|
|||
throws IOException {
|
||||
String includes = configProps.get(INCLUDES_PROPERTY);
|
||||
if (includes != null) {
|
||||
includes = substVars(includes, INCLUDES_PROPERTY, null, configProps, callback);
|
||||
includes = substVars(includes, INCLUDES_PROPERTY, configProps, callback);
|
||||
StringTokenizer st = new StringTokenizer(includes, "?\",", true);
|
||||
if (st.countTokens() > 0) {
|
||||
String location;
|
||||
|
@ -158,4 +159,9 @@ public class MavenPropertiesLoader {
|
|||
|
||||
return optional ? "?" + retVal : retVal;
|
||||
}
|
||||
|
||||
public static String substVars(
|
||||
String value, String name, Map<String, String> props, Function<String, String> callback) {
|
||||
return DefaultInterpolator.substVars(value, name, null, props, callback, null, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.io.StringWriter;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.maven.internal.impl.model.DefaultInterpolator;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -164,15 +165,16 @@ public class MavenPropertiesTest {
|
|||
@Test
|
||||
public void testConfigInterpolation() throws IOException {
|
||||
String config = "a=$\\\\\\\\{var}\n" + "ab=${a}b\n" + "abc=${ab}c";
|
||||
Map<String, String> expected = Map.of("a", "$\\{var}", "ab", "$\\{var}b", "abc", "$\\{var}bc");
|
||||
|
||||
java.util.Properties props1 = new java.util.Properties();
|
||||
props1.load(new StringReader(config));
|
||||
InterpolationHelper.performSubstitution((Map) props1, null);
|
||||
new DefaultInterpolator().performSubstitution((Map) props1, null, true);
|
||||
assertEquals(expected, props1);
|
||||
|
||||
MavenProperties props2 = new MavenProperties();
|
||||
props2.load(new StringReader(config));
|
||||
|
||||
assertEquals(props1, props2);
|
||||
assertEquals(expected, props2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.apache.maven.internal.impl.DefaultUrlNormalizer;
|
|||
import org.apache.maven.internal.impl.model.DefaultDependencyManagementImporter;
|
||||
import org.apache.maven.internal.impl.model.DefaultDependencyManagementInjector;
|
||||
import org.apache.maven.internal.impl.model.DefaultInheritanceAssembler;
|
||||
import org.apache.maven.internal.impl.model.DefaultInterpolator;
|
||||
import org.apache.maven.internal.impl.model.DefaultModelBuilder;
|
||||
import org.apache.maven.internal.impl.model.DefaultModelCacheFactory;
|
||||
import org.apache.maven.internal.impl.model.DefaultModelInterpolator;
|
||||
|
@ -1046,7 +1047,10 @@ public class MavenRepositorySystemSupplier implements Supplier<RepositorySystem>
|
|||
new DefaultModelValidator(),
|
||||
new DefaultModelNormalizer(),
|
||||
new DefaultModelInterpolator(
|
||||
new DefaultPathTranslator(), new DefaultUrlNormalizer(), new DefaultRootLocator()),
|
||||
new DefaultPathTranslator(),
|
||||
new DefaultUrlNormalizer(),
|
||||
new DefaultRootLocator(),
|
||||
new DefaultInterpolator()),
|
||||
new DefaultModelPathTranslator(new DefaultPathTranslator()),
|
||||
new DefaultModelUrlNormalizer(new DefaultUrlNormalizer()),
|
||||
new DefaultSuperPomProvider(modelProcessor),
|
||||
|
@ -1057,11 +1061,13 @@ public class MavenRepositorySystemSupplier implements Supplier<RepositorySystem>
|
|||
new DefaultDependencyManagementInjector(),
|
||||
new DefaultDependencyManagementImporter(),
|
||||
new DefaultPluginConfigurationExpander(),
|
||||
new ProfileActivationFilePathInterpolator(new DefaultPathTranslator(), new DefaultRootLocator()),
|
||||
new ProfileActivationFilePathInterpolator(
|
||||
new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()),
|
||||
new DefaultModelVersionParser(getVersionScheme()),
|
||||
List.of(),
|
||||
new DefaultModelCacheFactory(),
|
||||
new org.apache.maven.internal.impl.resolver.DefaultModelResolver());
|
||||
new org.apache.maven.internal.impl.resolver.DefaultModelResolver(),
|
||||
new DefaultInterpolator());
|
||||
}
|
||||
|
||||
private RepositorySystem repositorySystem;
|
||||
|
|
Loading…
Reference in New Issue