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