blob: 0c253246b534fc938ff79b79138a9db7bde458d2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* E.D.Willink - initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.examples.xtext.serializer;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Action;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CharacterRange;
import org.eclipse.xtext.CompoundElement;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.Grammar;
import org.eclipse.xtext.Group;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.TypeRef;
import org.eclipse.xtext.UntilToken;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.INode;
public class SerializationUtils
{
public static @NonNull String defaultIndentation = " ";
public static final @NonNull ENamedElementComparator ENAMED_ELEMENT_COMPARATOR = ENamedElementComparator.INSTANCE;
public static final @NonNull IndexedComparator INDEXED_COMPARATOR = IndexedComparator.INSTANCE;
public static final @NonNull NameableComparator NAMEABLE_COMPARATOR = NameableComparator.INSTANCE;
public static final class ENamedElementComparator implements Comparator<@NonNull ENamedElement>
{
public static final @NonNull ENamedElementComparator INSTANCE = new ENamedElementComparator();
@Override
public int compare(@NonNull ENamedElement o1, @NonNull ENamedElement o2) {
if (o1 == o2) {
return 0; // Short circuit containment compare / independent searches
}
String n1 = o1.getName();
String n2 = o2.getName();
int comparison = safeCompareTo(n1, n2);
if (comparison != 0) {
return comparison;
}
if ((o1 instanceof EPackage) && (o2 instanceof EPackage)) {
n1 = ((EPackage)o1).getNsURI();
n2 = ((EPackage)o2).getNsURI();
comparison = safeCompareTo(n1, n2);
if (comparison != 0) {
return comparison;
}
}
EObject p1 = o1.eContainer();
EObject p2 = o2.eContainer();
if ((p1 instanceof ENamedElement) && (p2 instanceof ENamedElement)) {
return compare((ENamedElement)p1, (ENamedElement)p2);
}
return comparison;
}
}
public static final class IndexedComparator implements Comparator<@NonNull Indexed>
{
public static final @NonNull IndexedComparator INSTANCE = new IndexedComparator();
@Override
public int compare(@NonNull Indexed o1, @NonNull Indexed o2) {
int n1 = o1.getIndex();
int n2 = o2.getIndex();
return n1 - n2;
}
}
public static final class NameableComparator implements Comparator<@NonNull Nameable>
{
public static final @NonNull NameableComparator INSTANCE = new NameableComparator();
@Override
public int compare(@NonNull Nameable o1, @NonNull Nameable o2) {
String n1 = o1.getName();
String n2 = o2.getName();
return safeCompareTo(n1, n2);
}
}
public static class ToStringComparator<T> implements Comparator<@NonNull T>
{
/**
* Provide a simple shared INSTANCE for comparison based on toString().
* If toString() is more expensive than a Map.get() a toString() cache can be
* activated by constructing a new ToStringComparator instance.
*/
public static final @NonNull ToStringComparator<@NonNull Object> INSTANCE = new ToStringComparator<@NonNull Object>(null);
/*
* toString can be expensive so avoid repeated evaluations.
*/
private final Map<@NonNull T, @NonNull String> object2string;
public ToStringComparator() {
this(new HashMap<>());
}
protected ToStringComparator(@Nullable Map<@NonNull T, @NonNull String> object2string) {
this.object2string = object2string;
}
@Override
public int compare(@NonNull T o1, @NonNull T o2) {
String s1;
String s2;
if (object2string == null) {
s1 = o1.toString();
s2 = o2.toString();
}
else {
s1 = getString(o1);
s2 = getString(o2);
}
return safeCompareTo(s1, s2);
}
protected @NonNull String getString(@NonNull T o) {
String string = maybeNull(object2string.get(o));
if (string == null) {
string = o.toString();
if (string == null) {
string = "";
}
object2string.put(o, string);
}
return string;
}
}
/**
* NodeIterator provides a depth first traversal of an Xtext INode tree, returning each IComposite node twice
* first with {@link isChildrenPending()} true for pre-order usage and later with {@link isChildrenPending()} false
* for post-order usage.
* <br/>
* The iterator is constructed with a particular node, which is returned by the first call to {@link next()}.
*/
public static class NodeIterator implements Iterator<@NonNull INode>
{
private @Nullable INode node;
private boolean hasNext;
private boolean childrenPending;
public NodeIterator(@NonNull INode node) {
this.node = node;
this.hasNext = true;
this.childrenPending = node instanceof ICompositeNode;
}
public NodeIterator(@NonNull NodeIterator nodeIterator) {
this.node = nodeIterator.node;
this.hasNext = nodeIterator.hasNext;
this.childrenPending = nodeIterator.childrenPending;
}
@Override
public boolean hasNext() {
if (hasNext) {
return true;
}
INode thisNode = node;
if (thisNode == null) {
return false;
}
// String thisText = thisNode.getText();
INode nextNode = null;
// String nextText;
if (childrenPending) {
nextNode = ((ICompositeNode)thisNode).getFirstChild();
// nextText = nextNode != null ? nextNode.getText() : null;
this.childrenPending = nextNode instanceof ICompositeNode;
}
if (nextNode == null) {
nextNode = thisNode.getNextSibling();
if (nextNode == null) {
nextNode = thisNode.getParent();
// nextText = nextNode != null ? nextNode.getText() : null;
this.childrenPending = false;
}
else {
// nextText = nextNode.getText();
this.childrenPending = nextNode instanceof ICompositeNode;
}
}
this.node = nextNode;
this.hasNext = nextNode != null;
return hasNext;
}
/**
* Return true if current node is an ICompositeNode in pre-order, else false if in post-order.
*/
public boolean isChildrenPending() {
assert node instanceof ICompositeNode;
return childrenPending;
}
@Override
public @NonNull INode next() {
this.hasNext = false;
if (node != null) {
return node;
}
throw new NoSuchElementException();
}
}
public static void appendIndentation(@NonNull StringBuilder s, int depth) {
appendIndentation(s, depth, defaultIndentation);
}
public static void appendIndentation(@NonNull StringBuilder s, int depth, @NonNull String string) {
if (depth >= 0) {
s.append("\n");
}
for (int i = 0; i < depth; i++) {
s.append(string);
}
}
public static @NonNull EClass eClass(@NonNull EObject eObject) {
return nonNullState(eObject.eClass());
}
public static @NonNull EStructuralFeature eContainingFeature(@NonNull EObject eObject) {
return nonNullState(eObject.eContainingFeature());
}
public static @NonNull EReference eContainmentFeature(@NonNull EObject eObject) {
return nonNullState(eObject.eContainmentFeature());
}
public static @NonNull EObject eContainer(@NonNull EObject eObject) {
return nonNullState(eObject.eContainer());
}
public static void formatDiagnostic(@NonNull StringBuilder s, @NonNull Diagnostic diagnostic, @NonNull String newLine) {
if (diagnostic.getSeverity() != Diagnostic.OK) {
s.append(newLine);
s.append(diagnostic.getSeverity() + " - ");
String location = diagnostic.getSource();
if (location != null) {
s.append(location);
s.append(": ");
}
s.append(diagnostic.getMessage());
for (Object obj : diagnostic.getData()) {
s.append(newLine);
s.append("\t");
// if (obj instanceof Throwable) {
// s.append(((Throwable)obj).getMessage());
// }
// else {
s.append(obj);
// }
}
for (Diagnostic childDiagnostic : diagnostic.getChildren()) {
if (childDiagnostic != null) {
formatDiagnostic(s, childDiagnostic, newLine + "\t");
}
}
}
}
public static String formatResourceDiagnostics(@NonNull List<Resource.Diagnostic> diagnostics, @NonNull String messagePrefix, @NonNull String newLine) {
if (diagnostics.size() <= 0) {
return null;
}
StringBuilder s = new StringBuilder();
s.append(messagePrefix);
for (Resource.Diagnostic diagnostic : diagnostics) {
if (diagnostic instanceof Diagnostic) {
formatDiagnostic(s, (Diagnostic)diagnostic, newLine);
}
else {
s.append(newLine);
String location = diagnostic.getLocation();
if (location != null) {
s.append(location);
s.append(":");
}
s.append(diagnostic.getLine());
try {
int column = diagnostic.getColumn();
if (column > 0) {
s.append(":");
s.append(column);
}
} catch (Exception e) {} // UnsupportedOperationException was normal for Bug 380232 fixed in Xtext 2.9
s.append(": ");
s.append(diagnostic.getMessage());
}
}
return s.toString();
}
public static @NonNull AbstractElement getAlternatives(@NonNull AbstractRule abstractRule) {
return nonNullState(abstractRule.getAlternatives());
}
public static @NonNull Iterable<@NonNull INode> getChildren(@NonNull ICompositeNode compositeNode) {
return nullFree(compositeNode.getChildren());
}
public static @NonNull EClassifier getClassifier(TypeRef type) {
return nonNullState(type.getClassifier());
}
public static @NonNull EClass getEClassScope(@NonNull AbstractElement abstractElement) {
TypeRef type = null;
for (EObject eObject = abstractElement, eChild = null; (type == null) && (eObject != null); eChild = eObject, eObject = eObject.eContainer()) {
if (eObject instanceof ParserRule) {
type = ((ParserRule)eObject).getType();
}
else if ((eObject instanceof Action) && (((Action)eObject).getFeature() != null)) {
type = ((Action)eObject).getType();
break;
}
else if ((eObject instanceof Group) && (eChild != null)) {
List<@NonNull AbstractElement> elements = getElements((Group)eObject);
int index = elements.indexOf(eChild);
assert index >= 0;
for (int i = index; --i >= 0; ) {
AbstractElement element = elements.get(i);
if (element instanceof Action) {
type = ((Action)element).getType();
break;
}
}
}
}
if (type != null) {
return (EClass)getClassifier(type);
}
throw new IllegalStateException();
}
public static @NonNull EClass getEContainingClass(@NonNull EStructuralFeature eFeature) {
return nonNullState(eFeature.getEContainingClass());
}
public static @NonNull EPackage getEPackage(@NonNull EClassifier eClassifier) {
return nonNullState(eClassifier.getEPackage());
}
public static @NonNull List<@NonNull AbstractElement> getElements(@NonNull CompoundElement compoundElement) {
return nullFree(compoundElement.getElements());
}
public static @NonNull Grammar getEContainingGrammar(@NonNull EObject eObject) {
for (EObject eCursor = eObject; (eCursor != null); eCursor = eCursor.eContainer()) {
if (eCursor instanceof Grammar) {
return (Grammar)eCursor;
}
}
throw new IllegalStateException();
}
public static @NonNull EClass getEReferenceType(@NonNull EReference eReference) {
return nonNullState(eReference.getEReferenceType());
}
public static @NonNull EStructuralFeature getEStructuralFeature(@NonNull EClass eClass, @NonNull String featureName) {
return nonNullState(eClass.getEStructuralFeature(featureName));
}
public static @NonNull EStructuralFeature getEStructuralFeature(@NonNull Assignment assignment) {
return getEStructuralFeature(getEClassScope(assignment), getFeature(assignment));
}
public static @NonNull EClass getSubTypeOf(@NonNull EClass thisEClass, @NonNull EClass thatEClass) {
if (thisEClass == thatEClass) {
return thisEClass;
}
else if (thisEClass.isSuperTypeOf(thatEClass)) {
return thatEClass;
}
else if (thatEClass.isSuperTypeOf(thisEClass)) {
return thisEClass;
}
else {
throw new IllegalStateException("No common subtype");
}
}
public static @NonNull String getFeature(@NonNull Action action) {
return nonNullState(action.getFeature());
}
public static @NonNull String getFeature(@NonNull Assignment assignment) {
return nonNullState(assignment.getFeature());
}
public static @NonNull Keyword getLeft(@NonNull CharacterRange characterRange) {
return nonNullState(characterRange.getLeft());
}
public static @NonNull String getName(@NonNull AbstractRule abstractRule) {
return nonNullState(abstractRule.getName());
}
public static @NonNull String getName(@NonNull ENamedElement eNamedElement) {
return nonNullState(eNamedElement.getName());
}
public static @NonNull Resource getResource(@NonNull EObject eObject) {
return nonNullState(eObject.eResource());
}
public static @NonNull Keyword getRight(@NonNull CharacterRange characterRange) {
return nonNullState(characterRange.getRight());
}
public static @NonNull AbstractRule getRule(@NonNull RuleCall ruleCall) {
return nonNullState(ruleCall.getRule());
}
public static @NonNull String getSafeName(@Nullable Nameable aNameable) {
if (aNameable == null) {
return "";
}
String name = aNameable.getName();
if (name == null) {
name = "";
}
return name;
}
public static @NonNull AbstractElement getTerminal(@NonNull Assignment assignment) {
return nonNullState(assignment.getTerminal());
}
public static @NonNull AbstractElement getTerminal(@NonNull CrossReference crossReference) {
return nonNullState(crossReference.getTerminal());
}
public static @NonNull AbstractElement getTerminal(@NonNull UntilToken untilToken) {
return nonNullState(untilToken.getTerminal());
}
public static @NonNull TypeRef getType(@NonNull AbstractRule abstractRule) {
return nonNullState(abstractRule.getType());
}
public static @NonNull TypeRef getType(@NonNull Action action) {
return nonNullState(action.getType());
}
public static @NonNull Iterable<@NonNull Grammar> getUsedGrammars(@NonNull Grammar grammar) {
return nullFree(grammar.getUsedGrammars());
}
public static @NonNull String getValue(@NonNull Keyword keyword) {
return nonNullState(keyword.getValue());
}
/**
* Return aT as @Nullable to suppress the compiler's insistence that the value is non-null.
* This may be necessary fo methods that access final non-null fields during construction.
*/
public static <T> @Nullable T maybeNull(T aT) {
return aT;
}
/**
* Check for an in appropriate program state. This should not happen, but is not impossible. For instance
* a Resource should be contained in a ResourceSet, but that doesn't mean it always is.
*<p>
* If the inappropriate state really cannot happen, an assertion should be used instead to avoid non-debug
* run-time cost.
* <p>
* Return aT, throwing an IllegalStateException if null.
*/
public static @NonNull <T> T nonNullState(@Nullable T aT) {
if (aT == null) {
throw new IllegalStateException();
}
return aT;
}
/**
* Cast a logically nullFreeList such as EMF collection to a declared null free list.
* @since 1.1
*/
@SuppressWarnings("null")
public static <T> @NonNull List<@NonNull T> nullFree(@Nullable List<T> nullFreeList) {
return nullFreeList != null ? nullFreeList : Collections.emptyList();
}
/**
* @since 1.1
*/
@SuppressWarnings({"null", "unchecked"})
public static <T> @NonNull EList<@NonNull T> nullFree(@Nullable EList<T> nullFreeList) {
return nullFreeList != null ? nullFreeList : (EList<T>) ECollections.EMPTY_ELIST;
}
/**
* @since 1.10
*/
@SuppressWarnings({"null", "unchecked"})
public static <T> @NonNull Iterable<@NonNull T> nullFree(@Nullable Iterable<T> nullFreeList) {
return nullFreeList != null ? nullFreeList : (Iterable<T>) ECollections.EMPTY_ELIST;
}
/**
* Safely determines the relative order of <code>object</code> and
* <code>otherObject</code>, i.e. without throwing an exception if
* <code>object</code> is <code>null</code>.
*/
public static <T extends Comparable<T>> int safeCompareTo(@Nullable T object, @Nullable T otherObject) {
if (object == null) {
return otherObject == null ? 0 : 1;
}
else {
return otherObject == null ? -1 : object.compareTo(otherObject);
}
}
/**
* Safely determines whether <code>object</code> equals
* <code>otherObject</code>, i.e. without throwing an exception if
* <code>object</code> is <code>null</code>.
*
* @param object
* The first object to compare.
* @param otherObject
* The second object to compare.
* @return <code>true</code> if <code>object</code> equals
* <code>otherObject</code>; <code>false</code> otherwise.
*/
public static boolean safeEquals(@Nullable Object object, @Nullable Object otherObject) {
return object == null
? otherObject == null
: object.equals(otherObject);
}
}