Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MNG-8563] Provide a weak cache for objects from the main model #2087

Merged
merged 6 commits into from
Feb 6, 2025
Merged
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
6 changes: 6 additions & 0 deletions api/maven-api-model/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ under the License.
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-xml</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,59 +20,117 @@

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* Class InputLocation.
* Tracks the source location of elements within Maven model files (such as pom.xml).
* This class stores information about where specific elements were defined, including
* line numbers, column numbers, and the source file information.
*
* <p>InputLocation instances are immutable and can be cached for performance optimization.
* They support tracking both direct locations and nested element locations through a map
* of field-specific locations.</p>
*
* <p>Usage example:</p>
* <pre>
* // Create a location for a source file
* InputSource source = InputSource.source("com.example:myproject:1.0", "pom.xml");
* InputLocation location = InputLocation.location(10, 15, source);
*
* // Get line and column information
* int line = location.getLineNumber(); // Returns 10
* int column = location.getColumnNumber(); // Returns 15
* </pre>
*
* <p>This class is particularly useful for:</p>
* <ul>
* <li>Error reporting with precise location information</li>
* <li>Tracking the origin of merged model elements</li>
* <li>Debugging model inheritance and interpolation</li>
* </ul>
*
* @see InputSource
* @see InputLocationTracker
*/
public class InputLocation implements Serializable, InputLocationTracker {
public class InputLocation implements Serializable, InputLocationTracker, Cacheable {
private final int lineNumber;
private final int columnNumber;
private final InputSource source;
private final Map<Object, InputLocation> locations;
private final InputLocation importedFrom;
private final int cacheHash;

public InputLocation(InputSource source) {
this.lineNumber = -1;
this.columnNumber = -1;
this.source = source;
this.locations = Collections.singletonMap(0, this);
this.importedFrom = null;
/**
* Creates a new InputLocation with only source information.
* Line and column numbers will be set to -1 to indicate they are not specified.
*
* @param source the input source containing file and model identification
* @return a cached instance of InputLocation
*/
public static InputLocation location(InputSource source) {
return CacheManager.getInstance().cached(new InputLocation(-1, -1, source, 0));
}

/**
* Creates a new InputLocation with line and column numbers but no source information.
* Useful for tracking positions in standalone files or when source information is not needed.
*
* @param lineNumber the 1-based line number in the source file
* @param columnNumber the 1-based column number in the source file
* @return a cached instance of InputLocation
*/
public static InputLocation location(int lineNumber, int columnNumber) {
return CacheManager.getInstance().cached(new InputLocation(lineNumber, columnNumber, null, null));
}

public InputLocation(int lineNumber, int columnNumber) {
this(lineNumber, columnNumber, null, null);
/**
* Creates a new InputLocation with line, column, and source information.
* This is the most common factory method for creating complete location information.
*
* @param lineNumber the 1-based line number in the source file
* @param columnNumber the 1-based column number in the source file
* @param source the input source containing file and model identification
* @return a cached instance of InputLocation
*/
public static InputLocation location(int lineNumber, int columnNumber, InputSource source) {
return CacheManager.getInstance().cached(new InputLocation(lineNumber, columnNumber, source, null));
}

public InputLocation(int lineNumber, int columnNumber, InputSource source) {
this(lineNumber, columnNumber, source, null);
/**
* Creates a new InputLocation with line, column, source, and self-location information.
* This factory method is useful when tracking locations of elements that reference themselves.
*
* @param lineNumber the 1-based line number in the source file
* @param columnNumber the 1-based column number in the source file
* @param source the input source containing file and model identification
* @param selfLocationKey the key to use for storing this location in its own locations map
* @return a cached instance of InputLocation
*/
public static InputLocation location(int lineNumber, int columnNumber, InputSource source, Object selfLocationKey) {
return CacheManager.getInstance().cached(new InputLocation(lineNumber, columnNumber, source, selfLocationKey));
}

public InputLocation(int lineNumber, int columnNumber, InputSource source, Object selfLocationKey) {
InputLocation(int lineNumber, int columnNumber, InputSource source, Object selfLocationKey) {
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
this.source = source;
this.locations =
selfLocationKey != null ? Collections.singletonMap(selfLocationKey, this) : Collections.emptyMap();
this.importedFrom = null;
this.locations = selfLocationKey != null
? ImmutableCollections.singletonMap(selfLocationKey, this)
: ImmutableCollections.emptyMap();
this.cacheHash = CacheManager.getInstance().computeCacheHash(this);
}

public InputLocation(int lineNumber, int columnNumber, InputSource source, Map<Object, InputLocation> locations) {
InputLocation(int lineNumber, int columnNumber, InputSource source, Map<Object, InputLocation> locations) {
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
this.source = source;
this.locations = ImmutableCollections.copy(locations);
this.importedFrom = null;
this.cacheHash = CacheManager.getInstance().computeCacheHash(this);
}

public InputLocation(InputLocation original) {
this.lineNumber = original.lineNumber;
this.columnNumber = original.columnNumber;
this.source = original.source;
this.locations = original.locations;
this.importedFrom = original.importedFrom;
@Override
public int cacheIdentityHash() {
return cacheHash;
}

public int getLineNumber() {
Expand Down Expand Up @@ -103,16 +161,24 @@ public Map<Object, InputLocation> getLocations() {
* @since 4.0.0
*/
public InputLocation getImportedFrom() {
return importedFrom;
return null;
}

/**
* Merges the {@code source} location into the {@code target} location.
* Merges two InputLocation instances, preserving location information from both sources.
* This is particularly useful when dealing with inherited or aggregated model elements.
*
* @param target the target location
* @param source the source location
* @param sourceDominant the boolean indicating of {@code source} is dominant compared to {@code target}
* @return the merged location
* <p>The merge strategy follows these rules:</p>
* <ul>
* <li>If either location is null, returns the non-null location</li>
* <li>Locations are combined based on the sourceDominant parameter</li>
* <li>Source information is merged using {@link InputSource#merge}</li>
* </ul>
*
* @param target the target location to merge into
* @param source the source location to merge from
* @param sourceDominant if true, source locations take precedence over target locations
* @return a new merged InputLocation instance
*/
public static InputLocation merge(InputLocation target, InputLocation source, boolean sourceDominant) {
if (source == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,52 @@
*/
package org.apache.maven.api.model;

import org.apache.maven.api.annotations.Nullable;

/**
* Provides a contract for tracking the source location of model elements.
* This interface is implemented by classes that need to maintain information
* about where their data was defined in source files.
*
* <p>The interface supports hierarchical location tracking, where elements can
* have both their own location and locations for their child elements. It also
* supports tracking locations of elements that have been imported from other
* files.</p>
*
* <p>Usage example:</p>
* <pre>
* public class ModelElement implements InputLocationTracker {
* private InputLocation location;
* private Map&lt;Object, InputLocation&gt; locations;
*
* public InputLocation getLocation(Object field) {
* return locations.get(field);
* }
*
* public InputLocation getImportedFrom() {
* return location.getImportedFrom();
* }
* }
* </pre>
*/
public interface InputLocationTracker {

/**
* Retrieves the location information for a specific field within the implementing class.
*
* @param field the identifier for the field whose location should be retrieved
* @return the InputLocation for the specified field, or null if not found
*/
InputLocation getLocation(Object field);

/**
* Gets the parent InputLocation where this InputLocation may have been imported from.
* Can return {@code null}.
* Retrieves the original location information when the current element was imported
* from another source. This is particularly useful for tracking the origin of
* inherited or merged model elements.
*
* @return InputLocation
* @return the InputLocation from which this element was imported, or null if not imported
* @since 4.0.0
*/
@Nullable
InputLocation getImportedFrom();
}
Loading