1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.impl.model;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.nio.file.FileVisitResult;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.PathMatcher;
27 import java.nio.file.Paths;
28 import java.nio.file.SimpleFileVisitor;
29 import java.nio.file.attribute.BasicFileAttributes;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.concurrent.atomic.AtomicBoolean;
36
37 import org.apache.maven.api.model.Model;
38 import org.apache.maven.api.services.Interpolator;
39 import org.apache.maven.api.services.InterpolatorException;
40 import org.apache.maven.api.services.ModelBuilderException;
41 import org.apache.maven.api.services.ProjectBuilderException;
42 import org.apache.maven.api.services.model.PathTranslator;
43 import org.apache.maven.api.services.model.ProfileActivationContext;
44 import org.apache.maven.api.services.model.RootLocator;
45
46
47
48
49 public class DefaultProfileActivationContext implements ProfileActivationContext {
50
51 record ExistRequest(String path, boolean enableGlob) {}
52
53 enum ModelInfo {
54 ArtifactId,
55 Packaging,
56 BaseDirectory,
57 RootDirectory
58 }
59
60
61
62
63
64
65 static class Record {
66 final Map<String, Boolean> usedActiveProfiles;
67 final Map<String, Boolean> usedInactiveProfiles;
68 final Map<String, String> usedSystemProperties;
69 final Map<String, String> usedUserProperties;
70 final Map<String, String> usedModelProperties;
71 final Map<ModelInfo, String> usedModelInfos;
72 final Map<ExistRequest, Boolean> usedExists;
73
74
75 Record() {
76 this.usedActiveProfiles = new HashMap<>();
77 this.usedInactiveProfiles = new HashMap<>();
78 this.usedSystemProperties = new HashMap<>();
79 this.usedUserProperties = new HashMap<>();
80 this.usedModelProperties = new HashMap<>();
81 this.usedModelInfos = new HashMap<>();
82 this.usedExists = new HashMap<>();
83 }
84
85
86 Record(Record source) {
87 this.usedActiveProfiles = Map.copyOf(source.usedActiveProfiles);
88 this.usedInactiveProfiles = Map.copyOf(source.usedInactiveProfiles);
89 this.usedSystemProperties = Map.copyOf(source.usedSystemProperties);
90 this.usedUserProperties = Map.copyOf(source.usedUserProperties);
91 this.usedModelProperties = Map.copyOf(source.usedModelProperties);
92 this.usedModelInfos = Map.copyOf(source.usedModelInfos);
93 this.usedExists = Map.copyOf(source.usedExists);
94 }
95
96 @Override
97 public boolean equals(Object o) {
98 if (o instanceof Record record) {
99 return Objects.equals(usedActiveProfiles, record.usedActiveProfiles)
100 && Objects.equals(usedInactiveProfiles, record.usedInactiveProfiles)
101 && Objects.equals(usedSystemProperties, record.usedSystemProperties)
102 && Objects.equals(usedUserProperties, record.usedUserProperties)
103 && Objects.equals(usedModelProperties, record.usedModelProperties)
104 && Objects.equals(usedModelInfos, record.usedModelInfos)
105 && Objects.equals(usedExists, record.usedExists);
106 }
107 return false;
108 }
109
110 @Override
111 public int hashCode() {
112 return Objects.hash(
113 usedActiveProfiles,
114 usedInactiveProfiles,
115 usedSystemProperties,
116 usedUserProperties,
117 usedModelProperties,
118 usedModelInfos,
119 usedExists);
120 }
121
122 boolean matches(DefaultProfileActivationContext context) {
123 return matchesProfiles(usedActiveProfiles, context.activeProfileIds)
124 && matchesProfiles(usedInactiveProfiles, context.inactiveProfileIds)
125 && matchesProperties(usedSystemProperties, context.systemProperties)
126 && matchesProperties(usedUserProperties, context.userProperties)
127 && matchesProperties(usedModelProperties, context.model.getProperties())
128 && matchesModelInfos(usedModelInfos, context)
129 && matchesExists(usedExists, context);
130 }
131
132 private boolean matchesProfiles(Map<String, Boolean> expected, List<String> actual) {
133 return expected.entrySet().stream()
134 .allMatch(e -> Objects.equals(e.getValue(), actual.contains(e.getKey())));
135 }
136
137 private boolean matchesProperties(Map<String, String> expected, Map<String, String> actual) {
138 return expected.entrySet().stream().allMatch(e -> Objects.equals(e.getValue(), actual.get(e.getKey())));
139 }
140
141 private boolean matchesModelInfos(Map<ModelInfo, String> infos, DefaultProfileActivationContext context) {
142 return infos.entrySet().stream()
143 .allMatch(e -> Objects.equals(e.getValue(), getModelValue(e.getKey(), context)));
144 }
145
146 private String getModelValue(ModelInfo key, DefaultProfileActivationContext context) {
147 return switch (key) {
148 case ArtifactId -> context.model.getArtifactId();
149 case Packaging -> context.model.getPackaging();
150 case BaseDirectory -> context.doGetModelBaseDirectory();
151 case RootDirectory -> context.doGetModelRootDirectory();
152 };
153 }
154
155 private boolean matchesExists(Map<ExistRequest, Boolean> exists, DefaultProfileActivationContext context) {
156 return exists.entrySet().stream()
157 .allMatch(e -> Objects.equals(
158 e.getValue(),
159 context.doExists(e.getKey().path(), e.getKey().enableGlob())));
160 }
161 }
162
163 private final PathTranslator pathTranslator;
164 private final RootLocator rootLocator;
165 private final Interpolator interpolator;
166
167 private List<String> activeProfileIds = Collections.emptyList();
168 private List<String> inactiveProfileIds = Collections.emptyList();
169 private Map<String, String> systemProperties = Collections.emptyMap();
170 private Map<String, String> userProperties = Collections.emptyMap();
171 private Model model;
172 final Record record;
173
174 public DefaultProfileActivationContext(
175 PathTranslator pathTranslator, RootLocator rootLocator, Interpolator interpolator) {
176 this.pathTranslator = pathTranslator;
177 this.rootLocator = rootLocator;
178 this.interpolator = interpolator;
179 this.record = null;
180 }
181
182 @SuppressWarnings("checkstyle:ParameterNumber")
183 public DefaultProfileActivationContext(
184 PathTranslator pathTranslator,
185 RootLocator rootLocator,
186 Interpolator interpolator,
187 List<String> activeProfileIds,
188 List<String> inactiveProfileIds,
189 Map<String, String> systemProperties,
190 Map<String, String> userProperties,
191 Model model) {
192 this(
193 pathTranslator,
194 rootLocator,
195 interpolator,
196 activeProfileIds,
197 inactiveProfileIds,
198 systemProperties,
199 userProperties,
200 model,
201 null);
202 }
203
204 @SuppressWarnings("checkstyle:ParameterNumber")
205 private DefaultProfileActivationContext(
206 PathTranslator pathTranslator,
207 RootLocator rootLocator,
208 Interpolator interpolator,
209 List<String> activeProfileIds,
210 List<String> inactiveProfileIds,
211 Map<String, String> systemProperties,
212 Map<String, String> userProperties,
213 Model model,
214 Record record) {
215 this.pathTranslator = pathTranslator;
216 this.rootLocator = rootLocator;
217 this.interpolator = interpolator;
218 this.activeProfileIds = activeProfileIds;
219 this.inactiveProfileIds = inactiveProfileIds;
220 this.systemProperties = systemProperties;
221 this.userProperties = userProperties;
222 this.model = model;
223 this.record = record;
224 }
225
226 DefaultProfileActivationContext start() {
227 return new DefaultProfileActivationContext(
228 pathTranslator,
229 rootLocator,
230 interpolator,
231 activeProfileIds,
232 inactiveProfileIds,
233 systemProperties,
234 userProperties,
235 model,
236 new Record());
237 }
238
239 Record stop() {
240
241 Objects.requireNonNull(record, "start() must be called before stop()");
242 record.usedActiveProfiles.values().removeIf(value -> !value);
243 record.usedInactiveProfiles.values().removeIf(value -> !value);
244 return new Record(record);
245 }
246
247 @Override
248 public boolean isProfileActive(String profileId) {
249 if (record != null) {
250 return record.usedActiveProfiles.computeIfAbsent(profileId, activeProfileIds::contains);
251 } else {
252 return activeProfileIds.contains(profileId);
253 }
254 }
255
256 @Override
257 public boolean isProfileInactive(String profileId) {
258 if (record != null) {
259 return record.usedInactiveProfiles.computeIfAbsent(profileId, inactiveProfileIds::contains);
260 } else {
261 return inactiveProfileIds.contains(profileId);
262 }
263 }
264
265 @Override
266 public String getSystemProperty(String key) {
267 if (record != null) {
268 return record.usedSystemProperties.computeIfAbsent(key, systemProperties::get);
269 } else {
270 return systemProperties.get(key);
271 }
272 }
273
274
275
276
277
278
279
280
281 public DefaultProfileActivationContext setSystemProperties(Map<String, String> systemProperties) {
282 this.systemProperties = unmodifiable(systemProperties);
283 return this;
284 }
285
286 @Override
287 public String getUserProperty(String key) {
288 if (record != null) {
289 return record.usedUserProperties.computeIfAbsent(key, userProperties::get);
290 } else {
291 return userProperties.get(key);
292 }
293 }
294
295
296
297
298
299
300
301
302
303 public DefaultProfileActivationContext setUserProperties(Map<String, String> userProperties) {
304 this.userProperties = unmodifiable(userProperties);
305 return this;
306 }
307
308 @Override
309 public String getModelArtifactId() {
310 if (record != null) {
311 return record.usedModelInfos.computeIfAbsent(ModelInfo.ArtifactId, k -> model.getArtifactId());
312 } else {
313 return model.getArtifactId();
314 }
315 }
316
317 @Override
318 public String getModelPackaging() {
319 if (record != null) {
320 return record.usedModelInfos.computeIfAbsent(ModelInfo.Packaging, k -> model.getPackaging());
321 } else {
322 return model.getPackaging();
323 }
324 }
325
326 @Override
327 public String getModelProperty(String key) {
328 if (record != null) {
329 return record.usedModelProperties.computeIfAbsent(
330 key, k -> model.getProperties().get(k));
331 } else {
332 return model.getProperties().get(key);
333 }
334 }
335
336 @Override
337 public String getModelBaseDirectory() {
338 if (record != null) {
339 return record.usedModelInfos.computeIfAbsent(ModelInfo.BaseDirectory, k -> doGetModelBaseDirectory());
340 } else {
341 return doGetModelBaseDirectory();
342 }
343 }
344
345 public String doGetModelBaseDirectory() {
346 Path basedir = model.getProjectDirectory();
347 return basedir != null ? basedir.toAbsolutePath().toString() : null;
348 }
349
350 @Override
351 public String getModelRootDirectory() {
352 if (record != null) {
353 return record.usedModelInfos.computeIfAbsent(ModelInfo.RootDirectory, k -> doGetModelRootDirectory());
354 } else {
355 return doGetModelRootDirectory();
356 }
357 }
358
359 private String doGetModelRootDirectory() {
360 Path basedir = model != null ? model.getProjectDirectory() : null;
361 Path rootdir = rootLocator != null ? rootLocator.findRoot(basedir) : null;
362 return rootdir != null ? rootdir.toAbsolutePath().toString() : null;
363 }
364
365 public DefaultProfileActivationContext setModel(Model model) {
366 this.model = model;
367 return this;
368 }
369
370 @Override
371 public String interpolatePath(String path) throws InterpolatorException {
372 if (path == null) {
373 return null;
374 }
375 String absolutePath = interpolator.interpolate(path, s -> {
376 if ("basedir".equals(s) || "project.basedir".equals(s)) {
377 return getModelBaseDirectory();
378 }
379 if ("project.rootDirectory".equals(s)) {
380 return getModelRootDirectory();
381 }
382 String r = getModelProperty(s);
383 if (r == null) {
384 r = getUserProperty(s);
385 }
386 if (r == null) {
387 r = getSystemProperty(s);
388 }
389 return r;
390 });
391 return pathTranslator.alignToBaseDirectory(absolutePath, model.getProjectDirectory());
392 }
393
394 @Override
395 public boolean exists(String path, boolean enableGlob) throws ModelBuilderException {
396 if (record != null) {
397 return record.usedExists.computeIfAbsent(
398 new ExistRequest(path, enableGlob), r -> doExists(r.path, r.enableGlob));
399 } else {
400 return doExists(path, enableGlob);
401 }
402 }
403
404 private boolean doExists(String path, boolean enableGlob) throws ModelBuilderException {
405 String pattern = interpolatePath(path);
406 String fixed, glob;
407 if (enableGlob) {
408 int asteriskIndex = pattern.indexOf('*');
409 int questionMarkIndex = pattern.indexOf('?');
410 int firstWildcardIndex = questionMarkIndex < 0
411 ? asteriskIndex
412 : asteriskIndex < 0 ? questionMarkIndex : Math.min(asteriskIndex, questionMarkIndex);
413 if (firstWildcardIndex < 0) {
414 fixed = pattern;
415 glob = "";
416 } else {
417 int lastSep = pattern.substring(0, firstWildcardIndex).lastIndexOf(File.separatorChar);
418 if (lastSep < 0) {
419 fixed = "";
420 glob = pattern;
421 } else {
422 fixed = pattern.substring(0, lastSep);
423 glob = pattern.substring(lastSep + 1);
424 }
425 }
426 } else {
427 fixed = pattern;
428 glob = "";
429 }
430 Path fixedPath = Paths.get(fixed);
431 return doExists(fixedPath, glob);
432 }
433
434 private static Boolean doExists(Path fixedPath, String glob) {
435 if (fixedPath == null || !Files.exists(fixedPath)) {
436 return false;
437 }
438 if (glob != null && !glob.isEmpty()) {
439 try {
440 PathMatcher matcher = fixedPath.getFileSystem().getPathMatcher("glob:" + glob);
441 AtomicBoolean found = new AtomicBoolean(false);
442 Files.walkFileTree(fixedPath, new SimpleFileVisitor<>() {
443 @Override
444 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
445 if (found.get() || matcher.matches(fixedPath.relativize(file))) {
446 found.set(true);
447 return FileVisitResult.TERMINATE;
448 }
449 return FileVisitResult.CONTINUE;
450 }
451 });
452 return found.get();
453 } catch (IOException e) {
454 throw new ProjectBuilderException(
455 "Unable to verify file existence for '" + glob + "' inside '" + fixedPath + "'", e);
456 }
457 }
458 return true;
459 }
460
461 private static Map<String, String> unmodifiable(Map<String, String> map) {
462 return map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap();
463 }
464 }