cdc.mf.model.MfLocationPart Maven / Gradle / Ivy
The newest version!
package cdc.mf.model;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import cdc.util.lang.Checks;
/**
* Local part of an element location. It is a triplet:
*
* - mandatory code, corresponding to the element class.
*
- optional element name.
*
- optional element id.
*
* The string representation is {@code code@name[id]}, where {@code name} and {@code id} are optional.
*/
public final class MfLocationPart {
/** The code pattern: only upper case letters. */
public static final Pattern CODE_PATTERN = Pattern.compile("[A-Z]+");
/** The id pattern */
public static final Pattern ID_PATTERN = Pattern.compile("[^\\[\\]]*");
public static final char SEPARATOR = '@';
public static final char OPEN = '[';
public static final char CLOSE = ']';
public static final String NULL = "";
/**
* Set of classes that are considered as non-readable and should be augmented with additional
* readable classes (their parent).
*/
private static final Set> NON_READABLE =
Set.of(MfDocumentation.class,
MfTag.class,
MfComposition.class,
MfAggregation.class,
MfAssociation.class,
MfSpecialization.class,
MfImplementation.class,
MfEnumerationValue.class);
/** The code associated to the element type. */
private final String code;
/** The local name of the element. */
private final String name;
/** The element id. */
private final String id;
/**
* Set of classes for which name is expected as a designator.
*/
private static final Set> NAME_IS_DESIGNATOR =
Set.of(MfClass.class,
MfEnumeration.class,
MfEnumerationValue.class,
MfInterface.class,
MfModel.class,
MfPackage.class,
MfParameter.class,
MfProperty.class);
private MfLocationPart(String code,
String name,
String id) {
this.code = Checks.isNotNull(code, "code");
this.name = name == null ? NULL : name;
this.id = id == null ? NULL : id;
Checks.isTrue(CODE_PATTERN.matcher(this.code).matches(), "Invalid code {}", this.code);
Checks.isTrue(ID_PATTERN.matcher(this.id).matches(), "Invalid id {}", this.id);
}
/**
* @param element The element.
* @return A new instance of MfLocationPart corresponding to {@code element}.
*/
public static MfLocationPart of(MfElement element) {
Checks.isNotNull(element, "element");
final String code = element.getCode();
final String name;
if (element instanceof final MfNameItem x) {
name = x.getName();
} else {
name = null;
}
return new MfLocationPart(code, name, element.getId());
}
/**
* @param s The string representation of the part.
* @return A new instance of MfLocationPart corresponding to {@code s}.
* @throws IllegalArgumentException When {@code s} can not be correctly interpreted.
*/
public static MfLocationPart of(String s) {
final int sepLoc = s.indexOf(SEPARATOR);
if (sepLoc > 0) {
final String code = s.substring(0, sepLoc);
final String tail = s.substring(sepLoc + 1);
final int openLoc = tail.lastIndexOf(OPEN);
if (openLoc >= 0) {
final String name = tail.substring(0, openLoc);
final String id = tail.substring(openLoc + 1, tail.length() - 1);
return new MfLocationPart(code, name, id);
}
}
throw new IllegalArgumentException("Invalid text: " + s);
}
/**
* @return The code.
*/
public String getCode() {
return code;
}
/**
* @return The element class, derived from code.
*/
public Class extends MfElement> getElementClass() {
return MfUtils.codeToClass(code);
}
public boolean hasValidName() {
return !name.isEmpty();
}
/**
* @return The element name. May be empty.
*/
public String getName() {
return name;
}
public boolean hasValidId() {
return !id.isEmpty();
}
/**
* @return The element id. May be empty.
*/
public String getId() {
return id;
}
/**
*
* @param parent The parent element.
* @param childPart The location part of the child element.
* @return The child of {@code parent} corresponding to {@code childPart},
* or {@code null} if {@code childPart} does not contain enough information to locate the child.
* @throws NoSuchElementException When there is enough information but no child was found.
* @throws ClassCastException When there is enough information, a child was found,
* but its class does not match the one defined by {@code childPart}.
*/
public static MfElement getChild(MfElement parent,
MfLocationPart childPart) {
final String code = childPart.getCode();
final Class extends MfElement> cls = MfUtils.codeToClass(code);
final String name = childPart.getName();
final MfElement result;
if (childPart.hasValidId()) {
final String id = childPart.getId();
result = parent.getModel().getItemWithId(id).orElseThrow();
} else if (childPart.hasValidName() && NAME_IS_DESIGNATOR.contains(cls)) {
result = parent.getChild(name, MfNameItem.class);
} else {
return null;
}
Checks.assertTrue(result.getParent() == parent, "parent mismatch");
return cls.cast(result);
}
@Override
public int hashCode() {
return Objects.hash(code,
name,
id);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof MfLocationPart)) {
return false;
}
final MfLocationPart other = (MfLocationPart) object;
return Objects.equals(code, other.code)
&& Objects.equals(name, other.name)
&& Objects.equals(id, other.id);
}
@Override
public String toString() {
return code + SEPARATOR + name + OPEN + id + CLOSE;
}
private static boolean isNonReadable(Class> cls) {
return NON_READABLE.contains(cls);
}
/**
* Takes a list of parts and keeps all parts starting at the last having an id.
*
* @param parts The parts.
* @param readable If true, adds additional elements to make thing more readable.
* @return A compressed version of {@code parts} that contains the same amount of information as {@code parts}.
*/
public static List compress(List parts,
boolean readable) {
if (parts.size() <= 1) {
return parts;
} else {
int from = parts.size() - 1;
// 1) Find last part with an id
while (from > 0 && !parts.get(from).hasValidId()) {
from--;
}
// 2) Find first previous part that is readable
while (from > 0 && isNonReadable(parts.get(from).getElementClass())) {
from--;
}
return parts.subList(from, parts.size());
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy