Skip to content

Commit 2408043

Browse files
committed
Squash
1 parent 16d9559 commit 2408043

File tree

8 files changed

+975
-21
lines changed

8 files changed

+975
-21
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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.model;
20+
21+
import java.util.Objects;
22+
import java.util.function.BiPredicate;
23+
24+
/**
25+
* Global dependency pool for automatic memory optimization.
26+
* <p>
27+
* This class provides automatic pooling of Dependency objects to reduce memory
28+
* consumption in large Maven projects. All Dependency instances created through
29+
* the Builder.build() method are automatically pooled.
30+
* <p>
31+
* The pool uses comprehensive equality comparison including all dependency fields
32+
* and InputLocation information to ensure correct behavior.
33+
* <p>
34+
* This class is package-protected and intended for internal use by generated model classes.
35+
*/
36+
class DependencyPool {
37+
38+
private static final ObjectPool<Dependency> POOL = new ObjectPool<>();
39+
40+
static {
41+
// Add shutdown hook to print statistics
42+
Runtime.getRuntime()
43+
.addShutdownHook(new Thread(
44+
() -> {
45+
long totalCalls = POOL.getTotalInternCalls();
46+
if (totalCalls > 0) {
47+
System.err.println("[INFO] DependencyPool Statistics: " + POOL.getStatistics());
48+
}
49+
},
50+
"DependencyPool-Statistics"));
51+
}
52+
53+
/**
54+
* Comprehensive equality predicate for dependencies.
55+
* <p>
56+
* Compares all dependency fields including:
57+
* - groupId, artifactId, version, type, classifier, scope
58+
* - optional flag
59+
* - exclusions list
60+
* - systemPath
61+
* - InputLocation (for tracking where the dependency was defined)
62+
*/
63+
private static final BiPredicate<Dependency, Dependency> DEPENDENCY_EQUALITY = (dep1, dep2) -> {
64+
if (dep1 == dep2) {
65+
return true;
66+
}
67+
if (dep1 == null || dep2 == null) {
68+
return false;
69+
}
70+
71+
return Objects.equals(dep1.getGroupId(), dep2.getGroupId())
72+
&& Objects.equals(dep1.getArtifactId(), dep2.getArtifactId())
73+
&& Objects.equals(dep1.getVersion(), dep2.getVersion())
74+
&& Objects.equals(dep1.getType(), dep2.getType())
75+
&& Objects.equals(dep1.getClassifier(), dep2.getClassifier())
76+
&& Objects.equals(dep1.getScope(), dep2.getScope())
77+
&& Objects.equals(dep1.getSystemPath(), dep2.getSystemPath())
78+
&& dep1.isOptional() == dep2.isOptional()
79+
&& Objects.equals(dep1.getExclusions(), dep2.getExclusions())
80+
&& Objects.equals(dep1.getLocation(""), dep2.getLocation(""));
81+
};
82+
83+
/**
84+
* Private constructor to prevent instantiation.
85+
*/
86+
private DependencyPool() {}
87+
88+
/**
89+
* Interns a dependency object in the global pool.
90+
* <p>
91+
* If an equivalent dependency already exists in the pool, that dependency is returned.
92+
* Otherwise, the provided dependency is added to the pool and returned.
93+
* <p>
94+
* This method is automatically called by Dependency.Builder.build() to provide
95+
* transparent memory optimization.
96+
*
97+
* @param dependency the dependency to intern
98+
* @return the pooled dependency (either existing or newly added)
99+
* @throws NullPointerException if dependency is null
100+
*/
101+
static Dependency intern(Dependency dependency) {
102+
return POOL.intern(dependency, DEPENDENCY_EQUALITY);
103+
}
104+
105+
/**
106+
* Returns the approximate number of dependencies currently in the pool.
107+
* <p>
108+
* This method is primarily useful for monitoring and debugging purposes.
109+
*
110+
* @return the approximate size of the dependency pool
111+
*/
112+
static int size() {
113+
return POOL.size();
114+
}
115+
116+
/**
117+
* Removes all dependencies from the pool.
118+
* <p>
119+
* This method is primarily useful for testing and cleanup purposes.
120+
* In normal operation, the pool should not need to be cleared as it uses
121+
* weak references that allow garbage collection when memory is needed.
122+
*/
123+
static void clear() {
124+
POOL.clear();
125+
}
126+
127+
/**
128+
* Returns true if the pool contains no dependencies.
129+
*
130+
* @return true if the pool is empty, false otherwise
131+
*/
132+
static boolean isEmpty() {
133+
return POOL.isEmpty();
134+
}
135+
}
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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.model;
20+
21+
import java.util.Objects;
22+
import java.util.concurrent.atomic.AtomicLong;
23+
import java.util.function.BiPredicate;
24+
25+
/**
26+
* A thread-safe object pool for reusing objects to reduce memory consumption.
27+
* <p>
28+
* This pool is particularly useful for immutable objects that are created frequently
29+
* and have a high likelihood of duplication, such as InputLocation objects in Maven builds.
30+
* <p>
31+
* The pool uses a custom equality predicate to determine if two objects are equivalent,
32+
* allowing for flexible comparison strategies beyond the standard equals() method.
33+
* <p>
34+
* This class is package-protected and intended for internal use by generated model classes.
35+
*
36+
* @param <T> the type of objects to pool
37+
*/
38+
class ObjectPool<T> {
39+
40+
private final SoftConcurrentMap<PoolKey<T>, T> pool = new SoftConcurrentMap<>();
41+
42+
/**
43+
* Statistics tracking for monitoring pool effectiveness.
44+
*/
45+
private final AtomicLong totalInternCalls = new AtomicLong(0);
46+
47+
private final AtomicLong cacheHits = new AtomicLong(0);
48+
49+
/**
50+
* Creates a new empty object pool.
51+
*/
52+
ObjectPool() {}
53+
54+
/**
55+
* Interns an object in the pool using a custom equality predicate.
56+
* <p>
57+
* If an equivalent object already exists in the pool, that object is returned.
58+
* Otherwise, the provided object is added to the pool and returned.
59+
*
60+
* @param object the object to intern
61+
* @param equalityPredicate predicate to test if two objects are equivalent
62+
* @return the pooled object (either existing or newly added)
63+
* @throws NullPointerException if object or equalityPredicate is null
64+
*/
65+
T intern(T object, BiPredicate<T, T> equalityPredicate) {
66+
Objects.requireNonNull(object, "object cannot be null");
67+
Objects.requireNonNull(equalityPredicate, "equalityPredicate cannot be null");
68+
69+
totalInternCalls.incrementAndGet();
70+
PoolKey<T> key = new PoolKey<>(object, equalityPredicate);
71+
T existing = pool.get(key);
72+
if (existing != null) {
73+
cacheHits.incrementAndGet();
74+
return existing;
75+
}
76+
return pool.computeIfAbsent(key, k -> object);
77+
}
78+
79+
/**
80+
* Retrieves an object from the pool if it exists.
81+
*
82+
* @param object the object to look for
83+
* @param equalityPredicate predicate to test if two objects are equivalent
84+
* @return the pooled object if it exists, null otherwise
85+
*/
86+
T getIfPresent(T object, BiPredicate<T, T> equalityPredicate) {
87+
if (object == null || equalityPredicate == null) {
88+
return null;
89+
}
90+
91+
PoolKey<T> key = new PoolKey<>(object, equalityPredicate);
92+
return pool.get(key);
93+
}
94+
95+
/**
96+
* Returns the approximate number of objects currently in the pool.
97+
*
98+
* @return the approximate size of the pool
99+
*/
100+
int size() {
101+
return pool.size();
102+
}
103+
104+
/**
105+
* Removes all objects from the pool and resets statistics.
106+
*/
107+
void clear() {
108+
pool.clear();
109+
totalInternCalls.set(0);
110+
cacheHits.set(0);
111+
}
112+
113+
/**
114+
* Returns true if the pool contains no objects.
115+
*
116+
* @return true if the pool is empty, false otherwise
117+
*/
118+
boolean isEmpty() {
119+
return pool.isEmpty();
120+
}
121+
122+
/**
123+
* Returns the total number of intern calls made to this pool.
124+
* This method is primarily for monitoring and debugging purposes.
125+
*
126+
* @return the total number of intern calls
127+
*/
128+
long getTotalInternCalls() {
129+
return totalInternCalls.get();
130+
}
131+
132+
/**
133+
* Returns the number of cache hits (objects found in pool).
134+
* This method is primarily for monitoring and debugging purposes.
135+
*
136+
* @return the number of cache hits
137+
*/
138+
long getCacheHits() {
139+
return cacheHits.get();
140+
}
141+
142+
/**
143+
* Returns the cache hit ratio as a percentage.
144+
* This method is primarily for monitoring and debugging purposes.
145+
*
146+
* @return the cache hit ratio (0.0 to 1.0)
147+
*/
148+
double getCacheHitRatio() {
149+
long total = totalInternCalls.get();
150+
return total > 0 ? (double) cacheHits.get() / total : 0.0;
151+
}
152+
153+
/**
154+
* Returns statistics about this pool.
155+
* This method is primarily for monitoring and debugging purposes.
156+
*
157+
* @return a string containing pool statistics
158+
*/
159+
String getStatistics() {
160+
long total = totalInternCalls.get();
161+
long hits = cacheHits.get();
162+
double hitRatio = total > 0 ? (double) hits / total : 0.0;
163+
return String.format(
164+
"ObjectPool[size=%d, totalCalls=%d, hits=%d, hitRatio=%.2f%%]",
165+
pool.size(), total, hits, hitRatio * 100);
166+
}
167+
168+
/**
169+
* Returns a string representation of this pool, including its size and statistics.
170+
*
171+
* @return a string representation of this pool
172+
*/
173+
@Override
174+
public String toString() {
175+
return getStatistics();
176+
}
177+
178+
/**
179+
* A wrapper class that combines an object with its equality predicate.
180+
* This allows us to use custom equality comparison for pooling.
181+
*/
182+
private static class PoolKey<T> {
183+
private final T object;
184+
private final BiPredicate<T, T> equalityPredicate;
185+
private final int hashCode;
186+
187+
PoolKey(T object, BiPredicate<T, T> equalityPredicate) {
188+
this.object = Objects.requireNonNull(object, "object cannot be null");
189+
this.equalityPredicate = Objects.requireNonNull(equalityPredicate, "equalityPredicate cannot be null");
190+
this.hashCode = object.hashCode();
191+
}
192+
193+
@Override
194+
public boolean equals(Object obj) {
195+
if (this == obj) {
196+
return true;
197+
}
198+
if (!(obj instanceof PoolKey<?> other)) {
199+
return false;
200+
}
201+
// Use the equality predicate to compare objects
202+
@SuppressWarnings("unchecked")
203+
PoolKey<T> otherKey = (PoolKey<T>) other;
204+
return equalityPredicate.test(this.object, otherKey.object);
205+
}
206+
207+
@Override
208+
public int hashCode() {
209+
return hashCode;
210+
}
211+
212+
@Override
213+
public String toString() {
214+
return "PoolKey{object=" + object + "}";
215+
}
216+
}
217+
}

0 commit comments

Comments
 (0)