Skip to content

Commit 5362d31

Browse files
committed
Interpolator service
1 parent 5df7ce0 commit 5362d31

File tree

32 files changed

+2627
-699
lines changed

32 files changed

+2627
-699
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.api.services;
20+
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.function.BiFunction;
24+
import java.util.function.Function;
25+
26+
import org.apache.maven.api.Service;
27+
import org.apache.maven.api.annotations.Experimental;
28+
import org.apache.maven.api.annotations.Nonnull;
29+
import org.apache.maven.api.annotations.Nullable;
30+
31+
/**
32+
* The Interpolator service provides methods for variable substitution in strings and maps.
33+
* It allows for the replacement of placeholders (e.g., ${variable}) with their corresponding values.
34+
*
35+
* @since 4.0.0
36+
*/
37+
@Experimental
38+
public interface Interpolator extends Service {
39+
40+
/**
41+
* Interpolates the values in the given map using the provided callback function.
42+
* This method defaults to setting empty strings for unresolved placeholders.
43+
*
44+
* @param properties The map containing key-value pairs to be interpolated.
45+
* @param callback The function to resolve variable values not found in the map.
46+
*/
47+
default void interpolate(@Nonnull Map<String, String> properties, @Nullable Function<String, String> callback) {
48+
interpolate(properties, callback, null, true);
49+
}
50+
51+
/**
52+
* Interpolates the values in the given map using the provided callback function.
53+
*
54+
* @param map The map containing key-value pairs to be interpolated.
55+
* @param callback The function to resolve variable values not found in the map.
56+
* @param defaultsToEmpty If true, unresolved placeholders are replaced with empty strings.
57+
*/
58+
default void interpolate(
59+
@Nonnull Map<String, String> map, @Nullable Function<String, String> callback, boolean defaultsToEmpty) {
60+
interpolate(map, callback, null, defaultsToEmpty);
61+
}
62+
63+
/**
64+
* Interpolates the values in the given map using the provided callback function.
65+
*
66+
* @param map The map containing key-value pairs to be interpolated.
67+
* @param callback The function to resolve variable values not found in the map.
68+
* @param defaultsToEmpty If true, unresolved placeholders are replaced with empty strings.
69+
*/
70+
void interpolate(
71+
@Nonnull Map<String, String> map,
72+
@Nullable Function<String, String> callback,
73+
@Nullable BiFunction<String, String, String> postprocessor,
74+
boolean defaultsToEmpty);
75+
76+
/**
77+
* Interpolates a single string value using the provided callback function.
78+
* This method defaults to not replacing unresolved placeholders.
79+
*
80+
* @param val The string to be interpolated.
81+
* @param callback The function to resolve variable values.
82+
* @return The interpolated string, or null if the input was null.
83+
*/
84+
@Nullable
85+
default String interpolate(@Nullable String val, @Nullable Function<String, String> callback) {
86+
return interpolate(val, callback, false);
87+
}
88+
89+
/**
90+
* Interpolates a single string value using the provided callback function.
91+
*
92+
* @param val The string to be interpolated.
93+
* @param callback The function to resolve variable values.
94+
* @param defaultsToEmpty If true, unresolved placeholders are replaced with empty strings.
95+
* @return The interpolated string, or null if the input was null.
96+
*/
97+
@Nullable
98+
default String interpolate(
99+
@Nullable String val, @Nullable Function<String, String> callback, boolean defaultsToEmpty) {
100+
return interpolate(val, callback, null, defaultsToEmpty);
101+
}
102+
103+
/**
104+
* Interpolates a single string value using the provided callback function.
105+
*
106+
* @param val The string to be interpolated.
107+
* @param callback The function to resolve variable values.
108+
* @param defaultsToEmpty If true, unresolved placeholders are replaced with empty strings.
109+
* @return The interpolated string, or null if the input was null.
110+
*/
111+
@Nullable
112+
String interpolate(
113+
@Nullable String val,
114+
@Nullable Function<String, String> callback,
115+
@Nullable BiFunction<String, String, String> postprocessor,
116+
boolean defaultsToEmpty);
117+
118+
/**
119+
* Creates a callback function that searches for values in two maps.
120+
*
121+
* @param map1 The first map to search.
122+
* @param map2 The second map to search.
123+
* @return A function that searches both maps for variable values.
124+
*/
125+
default Function<String, String> callback(Map<String, String> map1, Map<String, String> map2) {
126+
return callback(List.of(map1::get, map2::get));
127+
}
128+
129+
/**
130+
* Creates a callback function that searches for values in three maps.
131+
*
132+
* @param map1 The first map to search.
133+
* @param map2 The second map to search.
134+
* @param map3 The third map to search.
135+
* @return A function that searches all three maps for variable values.
136+
*/
137+
default Function<String, String> callback(
138+
Map<String, String> map1, Map<String, String> map2, Map<String, String> map3) {
139+
return callback(List.of(map1::get, map2::get, map3::get));
140+
}
141+
142+
/**
143+
* Creates a callback function that searches for values in multiple maps.
144+
*
145+
* @param maps An iterable of maps to search for variable values.
146+
* @return A function that searches all provided maps for variable values.
147+
*/
148+
Function<String, String> callback(Iterable<? extends Function<String, String>> maps);
149+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.api.services;
20+
21+
import java.io.Serial;
22+
23+
import org.apache.maven.api.annotations.Experimental;
24+
25+
/**
26+
* Exception thrown by {@link Interpolator} implementations when an error occurs during interpolation.
27+
* This can include syntax errors in variable placeholders or recursive variable references.
28+
*
29+
* @since 4.0.0
30+
*/
31+
@Experimental
32+
public class InterpolatorException extends MavenException {
33+
34+
@Serial
35+
private static final long serialVersionUID = -1219149033636851813L;
36+
37+
/**
38+
* Constructs a new InterpolatorException with {@code null} as its
39+
* detail message. The cause is not initialized, and may subsequently be
40+
* initialized by a call to {@link #initCause}.
41+
*/
42+
public InterpolatorException() {}
43+
44+
/**
45+
* Constructs a new InterpolatorException with the specified detail message.
46+
* The cause is not initialized, and may subsequently be initialized by
47+
* a call to {@link #initCause}.
48+
*
49+
* @param message the detail message. The detail message is saved for
50+
* later retrieval by the {@link #getMessage()} method.
51+
*/
52+
public InterpolatorException(String message) {
53+
super(message);
54+
}
55+
56+
/**
57+
* Constructs a new InterpolatorException with the specified detail message and cause.
58+
*
59+
* <p>Note that the detail message associated with {@code cause} is <i>not</i>
60+
* automatically incorporated in this exception's detail message.</p>
61+
*
62+
* @param message the detail message (which is saved for later retrieval
63+
* by the {@link #getMessage()} method).
64+
* @param cause the cause (which is saved for later retrieval by the
65+
* {@link #getCause()} method). A {@code null} value is
66+
* permitted, and indicates that the cause is nonexistent or unknown.
67+
*/
68+
public InterpolatorException(String message, Throwable cause) {
69+
super(message, cause);
70+
}
71+
}

maven-api-impl/pom.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,6 @@ under the License.
9191
<groupId>org.apache.maven</groupId>
9292
<artifactId>maven-xml-impl</artifactId>
9393
</dependency>
94-
<dependency>
95-
<groupId>org.codehaus.plexus</groupId>
96-
<artifactId>plexus-interpolation</artifactId>
97-
</dependency>
9894
<dependency>
9995
<groupId>com.fasterxml.woodstox</groupId>
10096
<artifactId>woodstox-core</artifactId>

maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import java.nio.file.Path;
2222

23+
import org.apache.maven.api.annotations.Nonnull;
24+
import org.apache.maven.api.annotations.Nullable;
2325
import org.apache.maven.api.model.Model;
2426
import org.apache.maven.api.services.ModelBuilderRequest;
2527
import org.apache.maven.api.services.ModelProblemCollector;
@@ -32,9 +34,7 @@
3234
public interface ModelInterpolator {
3335

3436
/**
35-
* Interpolates expressions in the specified model. Note that implementations are free to either interpolate the
36-
* provided model directly or to create a clone of the model and interpolate the clone. Callers should always use
37-
* the returned model and must not rely on the input model being updated.
37+
* Interpolates expressions in the specified model.
3838
*
3939
* @param model The model to interpolate, must not be {@code null}.
4040
* @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 {
4444
* @return The interpolated model, never {@code null}.
4545
* @since 4.0.0
4646
*/
47-
Model interpolateModel(Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems);
47+
@Nonnull
48+
Model interpolateModel(
49+
@Nonnull Model model,
50+
@Nullable Path projectDir,
51+
@Nonnull ModelBuilderRequest request,
52+
@Nonnull ModelProblemCollector problems);
4853
}

maven-api-impl/src/main/java/org/apache/maven/api/services/model/RootLocator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public interface RootLocator extends Service {
4242
+ " attribute on the root project's model to identify it.";
4343

4444
@Nonnull
45-
default Path findMandatoryRoot(Path basedir) {
45+
default Path findMandatoryRoot(@Nullable Path basedir) {
4646
Path rootDirectory = findRoot(basedir);
4747
if (rootDirectory == null) {
4848
throw new IllegalStateException(getNoRootMessage());
@@ -51,7 +51,7 @@ default Path findMandatoryRoot(Path basedir) {
5151
}
5252

5353
@Nullable
54-
default Path findRoot(Path basedir) {
54+
default Path findRoot(@Nullable Path basedir) {
5555
Path rootDirectory = basedir;
5656
while (rootDirectory != null && !isRootDirectory(rootDirectory)) {
5757
rootDirectory = rootDirectory.getParent();

maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultSettingsBuilder.java

Lines changed: 20 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@
2828
import java.nio.file.Paths;
2929
import java.util.ArrayList;
3030
import java.util.List;
31+
import java.util.Map;
32+
import java.util.function.Function;
3133

34+
import org.apache.maven.api.di.Inject;
3235
import org.apache.maven.api.di.Named;
3336
import org.apache.maven.api.services.BuilderProblem;
37+
import org.apache.maven.api.services.Interpolator;
3438
import org.apache.maven.api.services.SettingsBuilder;
3539
import org.apache.maven.api.services.SettingsBuilderException;
3640
import org.apache.maven.api.services.SettingsBuilderRequest;
@@ -44,12 +48,9 @@
4448
import org.apache.maven.api.settings.RepositoryPolicy;
4549
import org.apache.maven.api.settings.Server;
4650
import org.apache.maven.api.settings.Settings;
51+
import org.apache.maven.internal.impl.model.DefaultInterpolator;
4752
import org.apache.maven.settings.v4.SettingsMerger;
4853
import org.apache.maven.settings.v4.SettingsTransformer;
49-
import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
50-
import org.codehaus.plexus.interpolation.InterpolationException;
51-
import org.codehaus.plexus.interpolation.MapBasedValueSource;
52-
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
5354

5455
/**
5556
* Builds the effective settings from a user settings file and/or a global settings file.
@@ -62,6 +63,17 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
6263

6364
private final SettingsMerger settingsMerger = new SettingsMerger();
6465

66+
private final Interpolator interpolator;
67+
68+
public DefaultSettingsBuilder() {
69+
this(new DefaultInterpolator());
70+
}
71+
72+
@Inject
73+
public DefaultSettingsBuilder(Interpolator interpolator) {
74+
this.interpolator = interpolator;
75+
}
76+
6577
@Override
6678
public SettingsBuilderResult build(SettingsBuilderRequest request) throws SettingsBuilderException {
6779
List<BuilderProblem> problems = new ArrayList<>();
@@ -213,39 +225,10 @@ private Settings readSettings(
213225
}
214226

215227
private Settings interpolate(Settings settings, SettingsBuilderRequest request, List<BuilderProblem> problems) {
216-
217-
RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
218-
219-
interpolator.addValueSource(new MapBasedValueSource(request.getSession().getUserProperties()));
220-
221-
interpolator.addValueSource(new MapBasedValueSource(request.getSession().getSystemProperties()));
222-
223-
try {
224-
interpolator.addValueSource(new EnvarBasedValueSource());
225-
} catch (IOException e) {
226-
problems.add(new DefaultBuilderProblem(
227-
null,
228-
-1,
229-
-1,
230-
e,
231-
"Failed to use environment variables for interpolation: " + e.getMessage(),
232-
BuilderProblem.Severity.WARNING));
233-
}
234-
235-
return new SettingsTransformer(value -> {
236-
try {
237-
return value != null ? interpolator.interpolate(value) : null;
238-
} catch (InterpolationException e) {
239-
problems.add(new DefaultBuilderProblem(
240-
null,
241-
-1,
242-
-1,
243-
e,
244-
"Failed to interpolate settings: " + e.getMessage(),
245-
BuilderProblem.Severity.WARNING));
246-
return value;
247-
}
248-
})
228+
Map<String, String> userProperties = request.getSession().getUserProperties();
229+
Map<String, String> systemProperties = request.getSession().getSystemProperties();
230+
Function<String, String> src = interpolator.callback(userProperties, systemProperties);
231+
return new SettingsTransformer(value -> value != null ? interpolator.interpolate(value, src) : null)
249232
.visit(settings);
250233
}
251234

0 commit comments

Comments
 (0)