Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
*/
package org.apache.maven.internal.aether;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.maven.api.PathType;
import org.apache.maven.api.Type;
import org.apache.maven.api.services.TypeRegistry;
Expand All @@ -27,15 +30,25 @@

import static java.util.Objects.requireNonNull;

/**
* Adapter between Maven {@link TypeRegistry} and Resolver {@link ArtifactTypeRegistry}.
* <p>
* Results are cached per typeId since type definitions are immutable during a build.
*/
class TypeRegistryAdapter implements ArtifactTypeRegistry {
private final TypeRegistry typeRegistry;
private final ConcurrentMap<String, ArtifactType> cache = new ConcurrentHashMap<>();

TypeRegistryAdapter(TypeRegistry typeRegistry) {
this.typeRegistry = requireNonNull(typeRegistry, "typeRegistry");
}

@Override
public ArtifactType get(String typeId) {
return cache.computeIfAbsent(typeId, this::doGet);
}

private ArtifactType doGet(String typeId) {
Type type = typeRegistry.require(typeId);
if (type instanceof ArtifactType artifactType) {
return artifactType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,13 +555,15 @@ private List<ProjectBuildingResult> build(File pomFile, boolean recursive) {

List<ProjectBuildingResult> results = new ArrayList<>();
List<ModelBuilderResult> allModels = results(result).toList();
// All modules in the reactor share the same root directory —
// compute it once from the top-level pom instead of re-parsing
// pom.xml at every directory level for each of the N modules.
Path rootDirectory = rootLocator.findRoot(pomFile.getParentFile().toPath());
for (ModelBuilderResult r : allModels) {
if (r.getEffectiveModel() != null) {
File pom = r.getSource().getPath().toFile();
MavenProject project =
projectIndex.get(r.getEffectiveModel().getId());
Path rootDirectory =
rootLocator.findRoot(pom.getParentFile().toPath());
project.setRootDirectory(rootDirectory);
project.setFile(pom);
project.setExecutionRoot(pom.equals(pomFile));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
public interface InternalSession extends Session {

static InternalSession from(Session session) {
if (session instanceof InternalSession is) {
return is;
}
return cast(InternalSession.class, session, "session should be an " + InternalSession.class);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ public PropertiesAsMap(Map<Object, Object> properties) {
}

@Override
@SuppressWarnings("unchecked")
public Set<Entry<String, String>> entrySet() {
return new AbstractSet<Entry<String, String>>() {
@Override
public Iterator<Entry<String, String>> iterator() {
Iterator<Entry<Object, Object>> iterator = properties.entrySet().iterator();
return new Iterator<Entry<String, String>>() {
Entry<String, String> next;
Entry<Object, Object> next;

{
advance();
Expand All @@ -51,22 +52,7 @@ private void advance() {
while (iterator.hasNext()) {
Entry<Object, Object> e = iterator.next();
if (PropertiesAsMap.matches(e)) {
next = new Entry<String, String>() {
@Override
public String getKey() {
return (String) e.getKey();
}

@Override
public String getValue() {
return (String) e.getValue();
}

@Override
public String setValue(String value) {
return (String) e.setValue(value);
}
};
next = e;
break;
}
}
Expand All @@ -79,21 +65,26 @@ public boolean hasNext() {

@Override
public Entry<String, String> next() {
Entry<String, String> item = next;
Entry<Object, Object> item = next;
if (item == null) {
throw new NoSuchElementException();
}
advance();
return item;
// Safe cast: matches() guarantees both key and value are Strings.
return (Entry<String, String>) (Entry<?, ?>) item;
}
};
}

@Override
public int size() {
return (int) properties.entrySet().stream()
.filter(PropertiesAsMap::matches)
.count();
int count = 0;
for (Entry<Object, Object> e : properties.entrySet()) {
if (PropertiesAsMap.matches(e)) {
count++;
}
}
return count;
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,14 @@ public static String unescape(@Nullable String val) {
if (val == null || val.isEmpty()) {
return val;
}
val = val.replace(MARKER, "$");
// Fast path: if the string contains neither the escape marker ($__)
// nor the escape char (\), there is nothing to unescape.
if (val.indexOf(MARKER.charAt(0)) < 0 && val.indexOf(ESCAPE_CHAR) < 0) {
return val;
}
if (val.contains(MARKER)) {
val = val.replace(MARKER, "$");
}
int escape = val.indexOf(ESCAPE_CHAR);
while (escape >= 0 && escape < val.length() - 1) {
char c = val.charAt(escape + 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -113,9 +114,15 @@ private InnerInterpolator createInterpolator(
v -> Optional.ofNullable(callback(model, projectDir, request, problems, v));
UnaryOperator<String> cb = v -> cache.computeIfAbsent(v, ucb).orElse(null);
BinaryOperator<String> postprocessor = (e, v) -> postProcess(projectDir, request, e, v);
// Reuse a single HashSet for cycle detection across all strings in this model.
// The set is cleared after each substVars call returns, avoiding a new HashSet
// allocation per interpolated string (~550 allocations per Camel build).
HashSet<String> cycleMap = new HashSet<>();
DefaultInterpolator di = (DefaultInterpolator) interpolator;
return value -> {
try {
return interpolator.interpolate(value, cb, postprocessor, false);
cycleMap.clear();
return di.interpolate(value, null, cycleMap, cb, postprocessor, false);
} catch (InterpolatorException e) {
problems.add(BuilderProblem.Severity.ERROR, ModelProblem.Version.BASE, e.getMessage(), e);
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,26 @@ private void validateEffectiveDependencies(

String prefix = management ? "dependencyManagement.dependencies.dependency." : "dependencies.dependency.";

// Pre-compute scope validation data once before the loop instead of per-dependency.
// On Camel (676 modules, ~20+ deps each), this avoids thousands of redundant
// InternalSession.from() calls, stream pipelines, and array allocations.
String[] validScopes = null;
if (validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_2_0 && !dependencies.isEmpty()) {
ScopeManager scopeManager = InternalSession.from(s).getSession().getScopeManager();
if (management) {
Set<String> scopes = scopeManager.getDependencyScopeUniverse().stream()
.map(DependencyScope::getId)
.collect(Collectors.toCollection(HashSet::new));
scopes.add("import");
validScopes = scopes.toArray(new String[0]);
} else {
validScopes = scopeManager.getDependencyScopeUniverse().stream()
.map(DependencyScope::getId)
.distinct()
.toArray(String[]::new);
}
}

for (Dependency d : dependencies) {
validateEffectiveDependency(problems, d, management, prefix, validationLevel);

Expand All @@ -1237,12 +1257,6 @@ private void validateEffectiveDependencies(
SourceHint.dependencyManagementKey(d),
d);

/*
* Extensions like Flex Mojos use custom scopes like "merged", "internal", "external", etc. In
* order to not break backward-compat with those, only warn but don't error out.
*/
ScopeManager scopeManager =
InternalSession.from(s).getSession().getScopeManager();
validateDependencyScope(
prefix,
"scope",
Expand All @@ -1252,20 +1266,11 @@ private void validateEffectiveDependencies(
d.getScope(),
SourceHint.dependencyManagementKey(d),
d,
scopeManager.getDependencyScopeUniverse().stream()
.map(DependencyScope::getId)
.distinct()
.toArray(String[]::new),
validScopes,
false);

validateEffectiveModelAgainstDependency(prefix, problems, m, d);
} else {
ScopeManager scopeManager =
InternalSession.from(s).getSession().getScopeManager();
Set<String> scopes = scopeManager.getDependencyScopeUniverse().stream()
.map(DependencyScope::getId)
.collect(Collectors.toCollection(HashSet::new));
scopes.add("import");
validateDependencyScope(
prefix,
"scope",
Expand All @@ -1275,7 +1280,7 @@ private void validateEffectiveDependencies(
d.getScope(),
SourceHint.dependencyManagementKey(d),
d,
scopes.toArray(new String[0]),
validScopes,
true);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
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;
Expand All @@ -42,6 +41,8 @@
public class ReflectionValueExtractor {
private static final Object[] OBJECT_ARGS = new Object[0];

private static final List<String> ACCESSOR_PREFIXES = List.of("get", "is", "to", "as");

/**
* Use a WeakHashMap here, so the keys (Class objects) can be garbage collected.
* This approach prevents permgen space overflows due to retention of discarded
Expand Down Expand Up @@ -271,7 +272,7 @@ private static Object getPropertyValue(Object value, String property) throws Int
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")) {
for (String prefix : ACCESSOR_PREFIXES) {
Method method = classMap.findMethod(prefix + methodBase);
if (method != null) {
return method.invoke(value, OBJECT_ARGS);
Expand Down
5 changes: 5 additions & 0 deletions src/mdo/transformer.vm
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ public class ${className} {
* The transformation function.
*/
protected String transform(String value) {
// Fast path: skip the interpolation callback chain entirely for strings
// that cannot contain variable references (the vast majority in a typical model).
if (value == null || value.indexOf('$') < 0) {
return value;
}
return transformer.apply(value);
}

Expand Down
Loading