diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java index b802e8a44c1f..20341479c6e2 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java @@ -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; @@ -27,8 +30,14 @@ import static java.util.Objects.requireNonNull; +/** + * Adapter between Maven {@link TypeRegistry} and Resolver {@link ArtifactTypeRegistry}. + *

+ * Results are cached per typeId since type definitions are immutable during a build. + */ class TypeRegistryAdapter implements ArtifactTypeRegistry { private final TypeRegistry typeRegistry; + private final ConcurrentMap cache = new ConcurrentHashMap<>(); TypeRegistryAdapter(TypeRegistry typeRegistry) { this.typeRegistry = requireNonNull(typeRegistry, "typeRegistry"); @@ -36,6 +45,10 @@ class TypeRegistryAdapter implements ArtifactTypeRegistry { @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; diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java index 43785dee1321..b6ac66cbb395 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java +++ b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java @@ -560,9 +560,8 @@ private List build(File pomFile, boolean recursive) { File pom = r.getSource().getPath().toFile(); MavenProject project = projectIndex.get(r.getEffectiveModel().getId()); - Path rootDirectory = - rootLocator.findRoot(pom.getParentFile().toPath()); - project.setRootDirectory(rootDirectory); + project.setRootDirectory( + rootLocator.findRoot(pom.getParentFile().toPath())); project.setFile(pom); project.setExecutionRoot(pom.equals(pomFile)); initProject(project, r); diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java b/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java index e0f7d1c6e65c..63c9e2a65430 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java +++ b/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java @@ -1217,7 +1217,11 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(getGroupId(), getArtifactId(), getVersion()); + // Inlined hash avoids Object[] varargs allocation from Objects.hash() + int result = 31 + Objects.hashCode(getGroupId()); + result = 31 * result + Objects.hashCode(getArtifactId()); + result = 31 * result + Objects.hashCode(getVersion()); + return result; } public List getBuildExtensions() { diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/InternalSession.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/InternalSession.java index ae0635e4b69d..ccfd3611463e 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/InternalSession.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/InternalSession.java @@ -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); } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/PropertiesAsMap.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/PropertiesAsMap.java index cf71951c0956..0e0bc1aad394 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/PropertiesAsMap.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/PropertiesAsMap.java @@ -34,13 +34,14 @@ public PropertiesAsMap(Map properties) { } @Override + @SuppressWarnings("unchecked") public Set> entrySet() { return new AbstractSet>() { @Override public Iterator> iterator() { Iterator> iterator = properties.entrySet().iterator(); return new Iterator>() { - Entry next; + Entry next; { advance(); @@ -51,22 +52,7 @@ private void advance() { while (iterator.hasNext()) { Entry e = iterator.next(); if (PropertiesAsMap.matches(e)) { - next = new Entry() { - @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; } } @@ -79,21 +65,26 @@ public boolean hasNext() { @Override public Entry next() { - Entry item = next; + Entry item = next; if (item == null) { throw new NoSuchElementException(); } advance(); - return item; + // Safe cast: matches() guarantees both key and value are Strings. + return (Entry) (Entry) item; } }; } @Override public int size() { - return (int) properties.entrySet().stream() - .filter(PropertiesAsMap::matches) - .count(); + int count = 0; + for (Entry e : properties.entrySet()) { + if (PropertiesAsMap.matches(e)) { + count++; + } + } + return count; } }; } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInterpolator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInterpolator.java index 422a800f522e..f18a38a7f141 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInterpolator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInterpolator.java @@ -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); diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelInterpolator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelInterpolator.java index 2e8c7b1fe84a..e002f30c1dd9 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelInterpolator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelInterpolator.java @@ -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; @@ -102,7 +103,19 @@ interface InnerInterpolator { public Model interpolateModel( Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems) { InnerInterpolator innerInterpolator = createInterpolator(model, projectDir, request, problems); - return new MavenTransformer(innerInterpolator::interpolate).visit(model); + return new MavenTransformer(innerInterpolator::interpolate) { + @Override + protected String transform(String value) { + // Fast path: skip the interpolation callback chain for strings + // that cannot contain variable references (the vast majority). + // This is safe here because this transformer is only used for + // interpolation (${...}), NOT for decryption ({...}). + if (value == null || value.indexOf('$') < 0) { + return value; + } + return super.transform(value); + } + }.visit(model); } private InnerInterpolator createInterpolator( @@ -113,9 +126,15 @@ private InnerInterpolator createInterpolator( v -> Optional.ofNullable(callback(model, projectDir, request, problems, v)); UnaryOperator cb = v -> cache.computeIfAbsent(v, ucb).orElse(null); BinaryOperator 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 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; diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java index f17504b7d0fa..e503c1f200dc 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java @@ -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 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); @@ -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", @@ -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 scopes = scopeManager.getDependencyScopeUniverse().stream() - .map(DependencyScope::getId) - .collect(Collectors.toCollection(HashSet::new)); - scopes.add("import"); validateDependencyScope( prefix, "scope", @@ -1275,7 +1280,7 @@ private void validateEffectiveDependencies( d.getScope(), SourceHint.dependencyManagementKey(d), d, - scopes.toArray(new String[0]), + validScopes, true); } } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/reflection/ReflectionValueExtractor.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/reflection/ReflectionValueExtractor.java index dc6c8a613e4a..40563b11dd5c 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/reflection/ReflectionValueExtractor.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/reflection/ReflectionValueExtractor.java @@ -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; @@ -42,6 +41,8 @@ public class ReflectionValueExtractor { private static final Object[] OBJECT_ARGS = new Object[0]; + private static final List 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 @@ -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); diff --git a/src/mdo/transformer.vm b/src/mdo/transformer.vm index 05cc200e03e7..e1655e15b850 100644 --- a/src/mdo/transformer.vm +++ b/src/mdo/transformer.vm @@ -71,7 +71,7 @@ public class ${className} { * The transformation function. */ protected String transform(String value) { - return transformer.apply(value); + return value != null ? transformer.apply(value) : null; } #foreach ( $class in $model.allClasses )