cdc.mf.model.MfLocation Maven / Gradle / Ivy
package cdc.mf.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import cdc.issues.locations.AbstractLocation;
import cdc.issues.locations.Location;
import cdc.issues.locations.Locations;
import cdc.util.lang.Checks;
/**
* Implementation of {@link Location} dedicated to MF elements.
*
* It is equivalent to a list of {@link MfLocationPart MfLocationParts}.
* The full path is not encoded, just the necessary parts to reference an identified element
* and give enough information to user.
*
*/
public final class MfLocation extends AbstractLocation {
public static final Pattern PATH_PATTERN =
Pattern.compile("[A-Z]+@[^\\[]*\\[[^\\[\\]]*\\](/[A-Z]+@[^\\[]*\\[[^\\[\\]]*\\])*");
public static final char SEPARATOR = '/';
public static final String TAG = "MfLocation";
public static final String SEP = Character.toString(SEPARATOR);
private static final Pattern ID_PATTERN = Pattern.compile("\\[[^\\[\\]]*\\]");
static {
Locations.register(TAG, MfLocation::of);
}
private final String path;
private final String anchor;
private MfLocation(String path,
String anchor) {
this.path = Checks.isNotNullOrEmpty(path, "path");
this.anchor = anchor; // May be null
Checks.isTrue(PATH_PATTERN.matcher(path).matches(), "Invalid path: '" + path + "'");
}
private MfLocation(MfElement element,
String anchor) {
final List parts = element.getHierarchy()
.stream()
.map(MfElement::getLocationPart)
.toList();
this.path = MfLocationPart.compress(parts, true)
.stream()
.map(Object::toString)
.collect(Collectors.joining(SEP));
this.anchor = anchor;
}
public static void elaborate() {
// Ignore
}
/**
* @param element The element.
* @param anchor The anchor
* @return A new instance of MfLocation.
*/
public static MfLocation of(MfElement element,
String anchor) {
return new MfLocation(element, anchor);
}
/**
* @param element The element.
* @return A new instance of MfLocation.
*/
public static MfLocation of(MfElement element) {
return new MfLocation(element, null);
}
/**
* @param path The path.
* @param anchor The anchor.
* @return A new instance of MfLocation.
*/
public static MfLocation of(String path,
String anchor) {
return new MfLocation(path, anchor);
}
/**
* @param path The path.
* @return A new instance of MfLocation.
*/
public static MfLocation of(String path) {
return of(path, null);
}
@Override
public String getTag() {
return TAG;
}
@Override
public String getPath() {
return path;
}
@Override
public String getAnchor() {
return anchor;
}
/**
* @return The location parts encoded in the path.
*/
public List getParts() {
final List tmp = new ArrayList<>();
final Matcher m = ID_PATTERN.matcher(path);
int from = 0;
while (from < path.length()) {
final boolean found = m.find(from);
if (found) {
final int to = m.end();
final MfLocationPart next = MfLocationPart.of(path.substring(from, to));
tmp.add(next);
// Possibly skip '/'
if (to < path.length()) {
Checks.assertTrue(path.charAt(to) == SEPARATOR,
"Expected " + SEPARATOR + " at " + to + " in '" + path + "'");
}
from = to + 1;
} else {
final MfLocationPart next = MfLocationPart.of(path.substring(from));
tmp.add(next);
from = path.length();
}
}
return tmp;
}
/**
* Returns the element corresponding to this location in a model.
*
* It is the caller responsibility to make sure that the location and model are consistent.
*
* @param model The model.
* @return The element corresponding to this location in {@code model}.
*/
public Optional resolve(MfModel model) {
final List parts = getParts();
// Index of the last part that has a valid id
int lastValidId = parts.size() - 1;
while (lastValidId >= 0 && !parts.get(lastValidId).hasValidId()) {
lastValidId--;
}
if (lastValidId >= 0) {
final String id = parts.get(lastValidId).getId();
return model.getItemWithId(id);
} else {
return Optional.empty();
}
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object object) {
return super.equals(object);
}
}