cdc.mf.model.MfUtils Maven / Gradle / Ivy
package cdc.mf.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import cdc.args.Strictness;
import cdc.issues.locations.Location;
import cdc.util.encoding.ExtensionEncoder;
import cdc.util.lang.Introspection;
import cdc.util.paths.Path;
import cdc.util.strings.StringUtils;
public final class MfUtils {
@SuppressWarnings("rawtypes")
private static final ExtensionEncoder KINDS =
new ExtensionEncoder<>(Class.class, String.class);
@SuppressWarnings("rawtypes")
private static final ExtensionEncoder PLURALS =
new ExtensionEncoder<>(Class.class, String.class);
@SuppressWarnings("rawtypes")
private static final ExtensionEncoder CODES =
new ExtensionEncoder<>(Class.class, String.class);
private static final Map, Class>> PARENT_CLASSES = new HashMap<>();
private static final Map, Class>> BUILDER_CLASSES = new HashMap<>();
private static final Map, MfElementFeatures> FEATURES = new HashMap<>();
static {
put(MfAggregation.class,
MfAggregation.PARENT_CLASS,
MfAggregation.BUILDER_CLASS,
MfConnector.FEATURES,
"aggregation");
put(MfAnnotation.class,
MfAnnotation.PARENT_CLASS,
MfAnnotation.BUILDER_CLASS,
MfAnnotation.FEATURES,
"annotation");
put(MfAssociation.class,
MfAssociation.PARENT_CLASS,
MfAssociation.BUILDER_CLASS,
MfConnector.FEATURES,
"association");
put(MfClass.class,
MfClass.PARENT_CLASS,
MfClass.BUILDER_CLASS,
MfType.FEATURES,
"class",
"classes");
put(MfComposition.class,
MfComposition.PARENT_CLASS,
MfComposition.BUILDER_CLASS,
MfConnector.FEATURES,
"composition");
put(MfConstraint.class,
MfConstraint.PARENT_CLASS,
MfConstraint.BUILDER_CLASS,
MfConstraint.FEATURES,
"constraint");
put(MfDependency.class,
MfDependency.PARENT_CLASS,
MfDependency.BUILDER_CLASS,
MfDependency.FEATURES,
"dependency");
put(MfDocumentation.class,
MfDocumentation.PARENT_CLASS,
MfDocumentation.BUILDER_CLASS,
MfDocumentation.FEATURES,
"documentation");
put(MfEnumeration.class,
MfEnumeration.PARENT_CLASS,
MfEnumeration.BUILDER_CLASS,
MfType.FEATURES,
"enumeration");
put(MfEnumerationValue.class,
MfEnumerationValue.PARENT_CLASS,
MfEnumerationValue.BUILDER_CLASS,
MfEnumerationValue.FEATURES,
"value");
put(MfImplementation.class,
MfImplementation.PARENT_CLASS,
MfImplementation.BUILDER_CLASS,
MfImplementation.FEATURES,
"implementation");
put(MfInterface.class,
MfInterface.PARENT_CLASS,
MfInterface.BUILDER_CLASS,
MfType.FEATURES,
"interface");
put(MfModel.class,
null,
MfModel.BUILDER_CLASS,
MfModel.FEATURES,
"model");
put(MfOperation.class,
MfOperation.PARENT_CLASS,
MfOperation.BUILDER_CLASS,
MfOperation.FEATURES,
"operation");
put(MfPackage.class,
MfPackage.PARENT_CLASS,
MfPackage.BUILDER_CLASS,
MfPackage.FEATURES,
"package");
put(MfParameter.class,
MfParameter.PARENT_CLASS,
MfParameter.BUILDER_CLASS,
MfParameter.FEATURES,
"parameter");
put(MfProperty.class,
MfProperty.PARENT_CLASS,
MfProperty.BUILDER_CLASS,
MfProperty.FEATURES,
"property",
"properties");
put(MfSpecialization.class,
MfSpecialization.PARENT_CLASS,
MfSpecialization.BUILDER_CLASS,
MfSpecialization.FEATURES,
"specialization");
put(MfTag.class,
MfTag.PARENT_CLASS,
MfTag.BUILDER_CLASS,
MfTag.FEATURES,
"tag");
put(MfTip.class,
MfTip.PARENT_CLASS,
MfTip.BUILDER_CLASS,
MfTip.FEATURES,
"tip");
KINDS.lock();
PLURALS.lock();
CODES.lock();
}
private static >
void put(Class extends MfElement> childClass,
Class extends MfElement> parentClass,
Class extends MfAbstractElement.Builder, ?>> builderClass,
MfElementFeatures features,
String kind,
String plural) {
KINDS.put(childClass, kind);
PLURALS.put(childClass, plural);
CODES.put(childClass, kind.toUpperCase());
if (parentClass != null) {
PARENT_CLASSES.put(childClass, parentClass);
}
BUILDER_CLASSES.put(childClass, builderClass);
FEATURES.put(childClass, features);
}
private static >
void put(Class extends MfElement> childClass,
Class extends MfElement> parentClass,
Class extends MfAbstractElement.Builder, ?>> builderClass,
MfElementFeatures features,
String kind) {
put(childClass, parentClass, builderClass, features, kind, kind + "s");
}
private MfUtils() {
}
public static String validate(String s) {
return s == null ? "" : s;
}
public static Set> getConcreteElementClasses() {
return FEATURES.keySet();
}
public static >
Class
getParentClass(Class childClass) {
return Introspection.uncheckedCast(PARENT_CLASSES.get(childClass));
}
public static >
Class getBuilderClass(Class elementClass) {
return Introspection.uncheckedCast(BUILDER_CLASSES.get(elementClass));
}
public static MfElementFeatures getFeatures(Class extends MfElement> elementClass) {
return FEATURES.get(elementClass);
}
public static MfElementFeatures getFeatures(MfElement element) {
return getFeatures(element.getClass());
}
public static String getKind(Class extends MfElement> cls) {
return KINDS.encode(cls);
}
public static String getKindPlural(Class extends MfElement> cls) {
return PLURALS.encode(cls);
}
public static String getCode(Class extends MfElement> cls) {
return CODES.encode(cls);
}
/**
* @param item The item.
* @return A string representing the item kind.
*/
public static String getKind(MfElement item) {
return KINDS.encode(item.getClass());
}
/**
* @param item The item.
* @return A string representing the plural of item kind.
*/
public static String getKindPlural(MfElement item) {
return PLURALS.encode(item.getClass());
}
/**
* @param item The item.
* @return A string representing the item code.
*/
public static String getCode(MfElement item) {
return CODES.encode(item.getClass());
}
/**
* @param kind The kind.
* @return The class corresponding to {@code kind}.
*/
public static Class extends MfElement> kindToClass(String kind) {
@SuppressWarnings("unchecked")
final Class extends MfElement> tmp = KINDS.decode(kind);
return tmp;
}
/**
* @param code The code.
* @return The class corresponding to {@code code}.
*/
public static Class extends MfElement> codeToClass(String code) {
@SuppressWarnings("unchecked")
final Class extends MfElement> tmp = CODES.decode(code);
return tmp;
}
public static String identify(MfElement item) {
return Location.toString(item.getLocation(), false);
}
public static String toOrdering(boolean isOrdered) {
return isOrdered ? "ordered" : "not ordered";
}
public static String toNavigability(boolean isNavigable) {
return isNavigable ? "navigable" : "not navigable";
}
public static String toString(MfElement item) {
final StringBuilder builder = new StringBuilder();
if (item == null) {
builder.append("null");
} else {
builder.append(item.getClass().getSimpleName());
if (!StringUtils.isNullOrEmpty(item.getId())) {
builder.append(" (")
.append(item.getId())
.append(')');
}
if (item instanceof final MfNameItem x) {
builder.append(" '")
.append(x.getName())
.append('\'');
}
}
return builder.toString();
}
public static Path toQName(MfQNameItem item) {
if (item instanceof MfModel) {
return Path.ROOT;
} else {
if (item.getParent() instanceof MfModel) {
return Path.of(Path.SLASH + item.getName());
} else {
final Path parentPath = item.getParent().getQName();
return Path.of(parentPath.toStringSlash() + Path.SLASH + item.getName());
}
}
}
private static MfQNameItem resolve(MfQNameItem item,
Path path,
boolean fail) {
MfQNameItem index;
if (path.isAbsolute()) {
index = item.getModel();
} else {
index = item;
}
for (int i = 0; i < path.getNameCount(); i++) {
final String name = path.getName(i);
if (".".equals(name)) {
// Ignore
} else if ("..".equals(name)) {
index = index.getParent();
} else {
final List candidates =
index.getChildren()
.stream()
.filter(MfQNameItem.class::isInstance)
.map(MfQNameItem.class::cast)
.filter(x -> Objects.equals(name, x.getName()))
.toList();
if (candidates.size() == 1) {
index = candidates.get(0);
} else if (candidates.isEmpty()) {
if (fail) {
throw new NoSuchElementException(index + " has no child named '" + name + "'");
} else {
return null;
}
} else {
if (fail) {
throw new NoSuchElementException(index + " has several children named '" + name + "'");
} else {
return null;
}
}
}
}
return index;
}
public static MfQNameItem resolve(MfQNameItem item,
Path path) {
return resolve(item, path, true);
}
public static boolean canResolve(MfQNameItem item,
Path path) {
return resolve(item, path, false) != null;
}
private static MfUsage.Kind getInheritanceKind(MfType type,
MfType x) {
if (type instanceof MfInterface) {
if (x instanceof MfInterface) {
return MfUsage.Kind.EXTENSION;
} else {
return MfUsage.Kind.IMPLEMENTATION;
}
} else {
return MfUsage.Kind.EXTENSION;
}
}
private static boolean isInheritanceDirect(MfType type,
MfType x) {
final Set extends MfType> xAncestors = x.getDirectAncestors();
return xAncestors.contains(type);
}
/**
* Return the usages of an element.
*
* For a type:
*
* - Properties that use that type or one of its descendants.
*
- Parameters that use that type or one of its descendants.
*
- Types that extend that type.
*
- Navigable connectors to that type or one of its descendants.
*
*
* @param element The element.
* @param includeNonNavigableTips If {@code true}, include non-navigable tips.
* @return The usages of {@code element}.
*/
public static List getUsages(MfElement element,
boolean includeNonNavigableTips) {
final List list = new ArrayList<>();
final MfModel model = element.getModel();
if (element instanceof final MfType type) {
final Set extends MfType> descendants = type.getAllDescendants(Strictness.LOOSE);
final List extends MfType> sortedDescendants = new ArrayList<>(descendants);
Collections.sort(sortedDescendants, MfQNameItem.QNAME_COMPARATOR);
// Properties that are typed with that type or one of its descendants
model.traverse(MfProperty.class,
x -> list.add(new MfUsage(x,
MfUsage.Kind.PROPERTY_TYPE,
x.getType() == type)),
x -> x.hasValidType() && descendants.contains(x.getType()));
// Parameters that are typed with that type or one of its descendants
model.traverse(MfParameter.class,
x -> list.add(new MfUsage(x,
MfUsage.Kind.PARAMETER_TYPE,
x.getType() == type)),
x -> x.hasValidType() && descendants.contains(x.getType()));
// Types that extend or implement that type
for (final MfType descendant : sortedDescendants) {
if (descendant != type) {
list.add(new MfUsage(descendant,
getInheritanceKind(type, descendant),
isInheritanceDirect(type, descendant)));
}
}
// Navigable Tips
for (final MfType descendant : sortedDescendants) {
for (final MfConnector connector : descendant.getConnectors()) {
if (connector.getSourceTip().isNavigable()) {
list.add(new MfUsage(connector,
MfUsage.Kind.NAVIGABLE_SOURCE_TYPE,
connector.getSourceTip().getType() == type));
} else if (includeNonNavigableTips) {
list.add(new MfUsage(connector,
MfUsage.Kind.NON_NAVIGABLE_SOURCE_TYPE,
connector.getSourceTip().getType() == type));
}
}
for (final MfConnector connector : descendant.getReversedConnectors()) {
if (connector.getTargetTip().isNavigable()) {
list.add(new MfUsage(connector,
MfUsage.Kind.NAVIGABLE_TARGET_TYPE,
connector.getTargetTip().getType() == type));
} else if (includeNonNavigableTips) {
list.add(new MfUsage(connector,
MfUsage.Kind.NON_NAVIGABLE_TARGET_TYPE,
connector.getTargetTip().getType() == type));
}
}
}
}
return list;
}
}