diff --git a/.gitattributes b/.gitattributes
index 4cab1f4d2..7898b9e36 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,5 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
+
+*.emfatic text
+*.treeview text
diff --git a/.gitignore b/.gitignore
index 582412849..4c7b782dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,6 @@ hs_err_pid*
# default result file name, should never be commited
result/
result.zip
+
+# macOS
+*.DS_Store
diff --git a/README.md b/README.md
index cf0e1ce0d..0423a8904 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,8 @@ In the following, a list of all supported languages with their supported languag
| [Scala](https://www.scala-lang.org) | 2.13.8 | scala | beta | Scalameta |
| [Scheme](http://www.scheme-reports.org) | ? | scheme | unknown | JavaCC |
| [Swift](https://www.swift.org) | 5.4 | swift | beta | ANTLR 4 |
-| [EMF Metamodel](https://www.eclipse.org/modeling/emf/) | 2.25.0 | emf | alpha | EMF |
+| [EMF Metamodel](https://www.eclipse.org/modeling/emf/) | 2.25.0 | emf | beta | EMF |
+| [EMF Model](https://www.eclipse.org/modeling/emf/) | 2.25.0 | emf-model | alpha | EMF |
| Text (naive) | - | text | legacy | CoreNLP |
## Download and Installation
diff --git a/cli/pom.xml b/cli/pom.xml
index 60fd9621b..55826915d 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -97,6 +97,11 @@
emf-metamodel
${revision}
+
+ de.jplag
+ emf-model
+ ${revision}
+
org.kohsuke.metainf-services
diff --git a/cli/src/test/java/de/jplag/cli/LanguageTest.java b/cli/src/test/java/de/jplag/cli/LanguageTest.java
index 7ed7b9608..f6cb4913d 100644
--- a/cli/src/test/java/de/jplag/cli/LanguageTest.java
+++ b/cli/src/test/java/de/jplag/cli/LanguageTest.java
@@ -28,7 +28,7 @@ void testInvalidLanguage() throws Exception {
@Test
void testLoading() {
var languages = LanguageLoader.getAllAvailableLanguages();
- assertEquals(15, languages.size(), "Loaded Languages: " + languages.keySet());
+ assertEquals(16, languages.size(), "Loaded Languages: " + languages.keySet());
}
@Test
diff --git a/core/src/main/java/de/jplag/SubmissionSetBuilder.java b/core/src/main/java/de/jplag/SubmissionSetBuilder.java
index a9cb61b22..4d93c0d44 100644
--- a/core/src/main/java/de/jplag/SubmissionSetBuilder.java
+++ b/core/src/main/java/de/jplag/SubmissionSetBuilder.java
@@ -81,6 +81,13 @@ public SubmissionSet buildSubmissionSet() throws ExitException {
// Merge everything in a submission set.
List submissions = new ArrayList<>(foundSubmissions.values());
+
+ // Some languages expect a certain order, which is ensured here:
+ if (options.language().expectsSubmissionOrder()) {
+ List rootFiles = foundSubmissions.values().stream().map(it -> it.getRoot()).toList();
+ rootFiles = options.language().customizeSubmissionOrder(rootFiles);
+ submissions = new ArrayList<>(rootFiles.stream().map(foundSubmissions::get).toList());
+ }
return new SubmissionSet(submissions, baseCodeSubmission.orElse(null), options);
}
diff --git a/coverage-report/pom.xml b/coverage-report/pom.xml
index 21b94cc95..646bf87c4 100644
--- a/coverage-report/pom.xml
+++ b/coverage-report/pom.xml
@@ -101,6 +101,16 @@
emf-metamodel
${revision}
+
+ de.jplag
+ emf-metamodel-dynamic
+ ${revision}
+
+
+ de.jplag
+ emf-model
+ ${revision}
+
diff --git a/language-api/src/main/java/de/jplag/Language.java b/language-api/src/main/java/de/jplag/Language.java
index 892e30a8d..e6c42664c 100644
--- a/language-api/src/main/java/de/jplag/Language.java
+++ b/language-api/src/main/java/de/jplag/Language.java
@@ -66,4 +66,21 @@ default boolean useViewFiles() {
default String viewFileSuffix() {
return "";
}
+
+ /**
+ * Specifies if the submission order is relevant for this language.
+ * @return defaults to false.
+ */
+ default boolean expectsSubmissionOrder() {
+ return false;
+ }
+
+ /**
+ * Re-orders the provided submission according the requirements of the language.
+ * @param submissions is the list of submissions.
+ * @return the re-ordered list.
+ */
+ default List customizeSubmissionOrder(List submissions) {
+ return submissions;
+ }
}
diff --git a/language-api/src/main/java/de/jplag/Token.java b/language-api/src/main/java/de/jplag/Token.java
index 29a91610b..b279b886d 100644
--- a/language-api/src/main/java/de/jplag/Token.java
+++ b/language-api/src/main/java/de/jplag/Token.java
@@ -46,6 +46,16 @@ public Token(TokenType type, File file, int line, int column, int length) {
this.length = length;
}
+ /**
+ * Creates a token with column and length information.
+ * @param type is the token type.
+ * @param file is the name of the source code file.
+ * @param trace is the tracing information of the token, meaning line, column, and length.
+ */
+ public Token(TokenType type, File file, TokenTrace trace) {
+ this(type, file, trace.line(), trace.column(), trace.length());
+ }
+
/**
* Creates a token with column, length and semantic information.
* @param type is the token type.
diff --git a/language-api/src/main/java/de/jplag/TokenTrace.java b/language-api/src/main/java/de/jplag/TokenTrace.java
new file mode 100644
index 000000000..6fd9808f8
--- /dev/null
+++ b/language-api/src/main/java/de/jplag/TokenTrace.java
@@ -0,0 +1,17 @@
+package de.jplag;
+
+/**
+ * Tracing information to locate the corresponding code section of a token.
+ * @param line is the line index in the source code where the token resides. Index is 1-based.
+ * @param column is the column index, meaning where the token starts in the line. Index is 1-based.
+ * @param length is the length of the token in the source code.
+ */
+public record TokenTrace(int line, int column, int length) {
+
+ /**
+ * Creates a empty trace with line, column, and length set to {@link Token#NO_VALUE NO_VALUE}.
+ */
+ public TokenTrace() {
+ this(Token.NO_VALUE, Token.NO_VALUE, Token.NO_VALUE);
+ }
+}
diff --git a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/DynamicMetamodelToken.java b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/DynamicMetamodelToken.java
index e337baf3a..cf358cbfb 100644
--- a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/DynamicMetamodelToken.java
+++ b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/DynamicMetamodelToken.java
@@ -5,6 +5,7 @@
import org.eclipse.emf.ecore.EObject;
+import de.jplag.TokenTrace;
import de.jplag.TokenType;
import de.jplag.emf.MetamodelToken;
@@ -15,7 +16,7 @@
public class DynamicMetamodelToken extends MetamodelToken {
public DynamicMetamodelToken(TokenType type, File file, EObject eObject) {
- super(type, file, NO_VALUE, NO_VALUE, NO_VALUE, Optional.of(eObject));
+ super(type, file, new TokenTrace(), Optional.of(eObject));
}
public DynamicMetamodelToken(TokenType type, File file) {
diff --git a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/DynamicMetamodelTokenType.java b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/DynamicMetamodelTokenType.java
index 712dd9e86..27bcebf60 100644
--- a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/DynamicMetamodelTokenType.java
+++ b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/DynamicMetamodelTokenType.java
@@ -10,6 +10,7 @@ public DynamicMetamodelTokenType(EObject eObject) {
this(eObject.eClass());
}
+ @Override
public String getDescription() {
return eClass.getName();
}
diff --git a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/Language.java b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/Language.java
index 152461735..9f0635d64 100644
--- a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/Language.java
+++ b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/Language.java
@@ -1,25 +1,33 @@
package de.jplag.emf.dynamic;
-import org.kohsuke.MetaInfServices;
-
import de.jplag.emf.dynamic.parser.DynamicEcoreParser;
+import de.jplag.emf.parser.EcoreParser;
/**
* Language for EMF metamodels from the Eclipse Modeling Framework (EMF). This language is based on a dynamically
* created token set instead of a hand-picked one.
* @author Timur Saglam
*/
-@MetaInfServices(de.jplag.Language.class)
-public class Language extends de.jplag.emf.Language {
+public class Language extends de.jplag.emf.Language { // currently not included in the CLI
private static final String NAME = "EMF metamodels (dynamically created token set)";
private static final String IDENTIFIER = "emf-dynamic";
private static final int DEFAULT_MIN_TOKEN_MATCH = 10;
+ /**
+ * Creates an EMF language instance with a dynamic token parser.
+ */
public Language() {
super(new DynamicEcoreParser());
}
+ /**
+ * Creates an EMF language instance with a custom token parser.
+ */
+ public Language(EcoreParser parser) {
+ super(parser);
+ }
+
@Override
public String getName() {
return NAME;
diff --git a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicEcoreParser.java b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicEcoreParser.java
index c0477eb4f..2b355ac0f 100644
--- a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicEcoreParser.java
+++ b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicEcoreParser.java
@@ -1,10 +1,12 @@
package de.jplag.emf.dynamic.parser;
import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import de.jplag.TokenType;
import de.jplag.emf.MetamodelToken;
import de.jplag.emf.dynamic.DynamicMetamodelToken;
-import de.jplag.emf.dynamic.DynamicMetamodelTokenType;
+import de.jplag.emf.normalization.ModelSorter;
import de.jplag.emf.parser.EcoreParser;
import de.jplag.emf.util.AbstractMetamodelVisitor;
@@ -14,12 +16,24 @@
*/
public class DynamicEcoreParser extends EcoreParser {
+ private final DynamicElementTokenizer tokenizer;
+
+ public DynamicEcoreParser() {
+ tokenizer = new DynamicElementTokenizer();
+ }
+
@Override
protected AbstractMetamodelVisitor createMetamodelVisitor() {
- return new DynamicMetamodelTokenGenerator(this);
+ return new DynamicMetamodelTokenGenerator(this, tokenizer);
}
- public void addToken(DynamicMetamodelTokenType type, EObject source) {
+ @Override
+ protected void normalizeOrder(Resource modelResource) {
+ ModelSorter.sort(modelResource, tokenizer);
+ }
+
+ @Override
+ public void addToken(TokenType type, EObject source) {
MetamodelToken token = new DynamicMetamodelToken(type, currentFile, source);
MetamodelToken metadataEnrichedToken = treeView.convertToMetadataEnrichedToken(token);
tokens.add(metadataEnrichedToken);
diff --git a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicElementTokenizer.java b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicElementTokenizer.java
new file mode 100644
index 000000000..346771411
--- /dev/null
+++ b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicElementTokenizer.java
@@ -0,0 +1,38 @@
+package de.jplag.emf.dynamic.parser;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+
+import de.jplag.TokenType;
+import de.jplag.emf.dynamic.DynamicMetamodelTokenType;
+import de.jplag.emf.parser.ModelingElementTokenizer;
+
+/**
+ * Tokenizes any {@link EObject} via its {@link EClass}. Tracks all known tokens.
+ */
+public class DynamicElementTokenizer implements ModelingElementTokenizer {
+
+ private final Set knownTokenTypes;
+
+ /**
+ * Creates the tokenizer, initially with an empty token set.
+ */
+ public DynamicElementTokenizer() {
+ knownTokenTypes = new HashSet<>();
+ }
+
+ @Override
+ public TokenType element2Token(EObject modelElement) {
+ DynamicMetamodelTokenType token = new DynamicMetamodelTokenType(modelElement);
+ knownTokenTypes.add(token);
+ return token;
+ }
+
+ @Override
+ public Set allTokenTypes() {
+ return Set.copyOf(knownTokenTypes);
+ }
+}
diff --git a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicMetamodelTokenGenerator.java b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicMetamodelTokenGenerator.java
index 718a18457..d77f17e1d 100644
--- a/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicMetamodelTokenGenerator.java
+++ b/languages/emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/parser/DynamicMetamodelTokenGenerator.java
@@ -2,7 +2,6 @@
import org.eclipse.emf.ecore.EObject;
-import de.jplag.emf.dynamic.DynamicMetamodelTokenType;
import de.jplag.emf.util.AbstractMetamodelVisitor;
/**
@@ -11,19 +10,19 @@
*/
public class DynamicMetamodelTokenGenerator extends AbstractMetamodelVisitor {
private final DynamicEcoreParser parser;
+ private final DynamicElementTokenizer tokenizer;
/**
* Creates the visitor.
* @param parser is the parser which receives the generated tokens.
*/
- public DynamicMetamodelTokenGenerator(DynamicEcoreParser parser) {
- super(false);
+ public DynamicMetamodelTokenGenerator(DynamicEcoreParser parser, DynamicElementTokenizer tokenizer) {
this.parser = parser;
+ this.tokenizer = tokenizer;
}
@Override
protected void visitEObject(EObject eObject) {
- var tokenType = new DynamicMetamodelTokenType(eObject);
- parser.addToken(tokenType, eObject);
+ parser.addToken(tokenizer.element2Token(eObject), eObject);
}
}
diff --git a/languages/emf-metamodel-dynamic/src/test/java/de/jplag/emf/dynamic/MinimalDynamicMetamodelTest.java b/languages/emf-metamodel-dynamic/src/test/java/de/jplag/emf/dynamic/MinimalDynamicMetamodelTest.java
index a5cc19562..2861a6edd 100644
--- a/languages/emf-metamodel-dynamic/src/test/java/de/jplag/emf/dynamic/MinimalDynamicMetamodelTest.java
+++ b/languages/emf-metamodel-dynamic/src/test/java/de/jplag/emf/dynamic/MinimalDynamicMetamodelTest.java
@@ -13,6 +13,7 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,6 +43,7 @@ public void setUp() {
}
@Test
+ @DisplayName("Test tokens generated from example metamodels")
void testBookstoreMetamodels() throws ParsingException {
List testFiles = Arrays.stream(TEST_SUBJECTS).map(path -> new File(BASE_PATH.toFile(), path)).toList();
List result = language.parse(new HashSet<>(testFiles));
diff --git a/languages/emf-metamodel/pom.xml b/languages/emf-metamodel/pom.xml
index 0e5f502b1..8b328c38d 100644
--- a/languages/emf-metamodel/pom.xml
+++ b/languages/emf-metamodel/pom.xml
@@ -12,7 +12,7 @@
org.eclipse.emfatic
org.eclipse.emfatic.core
- 1.0.0
+ ${emfatic.version}
org.eclipse.emf
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/MetamodelToken.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/MetamodelToken.java
index 528fb94f1..0bc6544cf 100644
--- a/languages/emf-metamodel/src/main/java/de/jplag/emf/MetamodelToken.java
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/MetamodelToken.java
@@ -6,6 +6,7 @@
import org.eclipse.emf.ecore.EObject;
import de.jplag.Token;
+import de.jplag.TokenTrace;
import de.jplag.TokenType;
/**
@@ -22,8 +23,8 @@ public class MetamodelToken extends Token {
* @param file is the source model file.
* @param eObject is the corresponding eObject in the model from which this token was extracted.
*/
- public MetamodelToken(MetamodelTokenType type, File file, EObject eObject) {
- this(type, file, NO_VALUE, NO_VALUE, NO_VALUE, Optional.of(eObject));
+ public MetamodelToken(TokenType type, File file, EObject eObject) {
+ this(type, file, new TokenTrace(), Optional.of(eObject));
}
/**
@@ -32,20 +33,18 @@ public MetamodelToken(MetamodelTokenType type, File file, EObject eObject) {
* @param file is the source model file.
*/
public MetamodelToken(TokenType type, File file) {
- this(type, file, NO_VALUE, NO_VALUE, NO_VALUE, Optional.empty());
+ this(type, file, new TokenTrace(), Optional.empty());
}
/**
* Creates a token with column and length information.
* @param type is the token type.
* @param file is the source code file.
- * @param line is the line index in the source code where the token resides. Cannot be smaller than 1.
- * @param column is the column index, meaning where the token starts in the line.
- * @param length is the length of the token in the source code.
+ * @param trace is the tracing information of the token, meaning line, column, and length.
* @param eObject is the corresponding eObject in the model from which this token was extracted
*/
- public MetamodelToken(TokenType type, File file, int line, int column, int length, Optional eObject) {
- super(type, file, line, column, length);
+ public MetamodelToken(TokenType type, File file, TokenTrace trace, Optional eObject) {
+ super(type, file, trace);
this.eObject = eObject;
}
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/MetamodelTokenType.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/MetamodelTokenType.java
index a12596ea4..c90c34571 100644
--- a/languages/emf-metamodel/src/main/java/de/jplag/emf/MetamodelTokenType.java
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/MetamodelTokenType.java
@@ -16,15 +16,15 @@ public enum MetamodelTokenType implements TokenType {
ENUM_END(ENUM),
ENUM_LITERAL("EEnumLiteral"),
OPERATION("EOperation"),
- OPERATION_END(OPERATION),
REFERENCE("EReference"),
+ REFERENCE_MULT("EReference (multi-valued)"),
ATTRIBUTE("EAttribute"),
PARAMETER("EParameter"),
INTERFACE("EInterface"),
INTERFACE_END(INTERFACE),
- SUPER_TYPE("ESuperType"),
ID_ATTRIBUTE("EAttribute (ID)"),
CONTAINMENT("EReference (Containment)"),
+ CONTAINMENT_MULT("EReference (Containment, multi-valued)"),
ABSTRACT_CLASS("EAbstractClass"),
ABSTRACT_CLASS_END(ABSTRACT_CLASS),
RETURN_TYPE("EClassifier (Return Type"),
@@ -37,6 +37,7 @@ public enum MetamodelTokenType implements TokenType {
private final String description;
private final boolean isEndToken;
+ @Override
public String getDescription() {
return description;
}
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/normalization/ContainmentOrderNormalizer.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/normalization/ContainmentOrderNormalizer.java
new file mode 100644
index 000000000..f8dc57934
--- /dev/null
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/normalization/ContainmentOrderNormalizer.java
@@ -0,0 +1,132 @@
+package de.jplag.emf.normalization;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.emf.ecore.EObject;
+
+import de.jplag.TokenType;
+import de.jplag.emf.parser.ModelingElementTokenizer;
+
+/**
+ * Comparator for normalizing the order in a model tree by sorting the elements of containment references according to
+ * their token type and then according to the distributions of token types in their subtrees.
+ */
+public class ContainmentOrderNormalizer implements Comparator {
+
+ private final List modelElementsToSort;
+ private final Map> paths;
+ private final ModelingElementTokenizer tokenizer;
+ private final TokenVectorGenerator tokenVectorGenerator;
+
+ /**
+ * Creates the normalizing comparator.
+ * @param modelElementsToSort are all model elements to sort with the comparator (required for normalization process).
+ */
+ public ContainmentOrderNormalizer(List modelElementsToSort, ModelingElementTokenizer tokenizer) {
+ this.modelElementsToSort = modelElementsToSort;
+ this.tokenizer = tokenizer;
+ paths = new HashMap<>();
+ tokenVectorGenerator = new TokenVectorGenerator(tokenizer);
+ }
+
+ @Override
+ public int compare(EObject first, EObject second) {
+ TokenType firstType = tokenizer.element2Token(first);
+ TokenType secondType = tokenizer.element2Token(second);
+
+ // 0. comparison if token types are absent for one or more elements.
+ if (firstType == null && secondType == null) {
+ return 0;
+ } else if (firstType == null) {
+ return -1;
+ } else if (secondType == null) {
+ return 1;
+ }
+
+ // 1. comparison by token type
+ int comparisonByType = firstType.toString().compareTo(secondType.toString());
+ if (comparisonByType != 0) {
+ return comparisonByType;
+ }
+
+ // 2. compare by position of the nearest neighbor path of the token distribution vectors of the elements subtrees.
+ List path = paths.computeIfAbsent(firstType, this::calculatePath);
+ return path.indexOf(first) - path.indexOf(second);
+ }
+
+ private List calculatePath(List elements, EObject start, double[][] distances) {
+ List path = new ArrayList<>();
+ Set remaining = new HashSet<>(elements);
+ EObject current = start;
+ remaining.remove(current);
+ path.add(current);
+ while (!remaining.isEmpty()) {
+ double shortestDistance = Double.MAX_VALUE;
+ EObject next = null;
+ for (EObject potentialNext : remaining) {
+ double distance = distances[elements.indexOf(current)][elements.indexOf(potentialNext)];
+ if (distance < shortestDistance) {
+ shortestDistance = distance;
+ next = potentialNext;
+ } else if (distance == shortestDistance && modelElementsToSort.indexOf(potentialNext) < modelElementsToSort.indexOf(next)) {
+ next = potentialNext; // Sort according to original order if equal
+ }
+ }
+ current = next;
+ remaining.remove(current);
+ path.add(current);
+ }
+ return path;
+ }
+
+ private List calculatePath(TokenType type) {
+ List elements = modelElementsToSort.stream().filter(it -> type.equals(tokenizer.element2Token(it))).toList();
+
+ // Generate token type distributions for the subtrees of the elements to sort:
+ Map> subtreeVectors = new HashMap<>();
+ elements.forEach(it -> subtreeVectors.put(it, tokenVectorGenerator.generateOccurenceVector(it.eAllContents())));
+
+ // Calculate distance matrix:
+ double[][] distances = new double[elements.size()][elements.size()];
+ for (int from = 0; from < distances.length; from++) {
+ for (int to = 0; to < distances.length; to++) {
+ distances[from][to] = euclideanDistance(subtreeVectors.get(elements.get(from)), subtreeVectors.get(elements.get(to)));
+ }
+ }
+
+ // Start with element that has the most tokens in the subtree:
+ var max = Collections.max(elements, (first, second) -> Integer.compare(countSubtreeTokens(first), countSubtreeTokens(second)));
+ return calculatePath(elements, max, distances);
+ }
+
+ private int countSubtreeTokens(EObject modelElement) {
+ int count = 0;
+ Iterator iterator = modelElement.eAllContents();
+ while (iterator.hasNext()) {
+ if (tokenizer.element2Token(iterator.next()) != null) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private static double euclideanDistance(List first, List second) {
+ if (first.size() != second.size()) {
+ throw new IllegalArgumentException("Lists must have the same size");
+ }
+ double sum = 0;
+ for (int i = 0; i < first.size(); i++) {
+ double diff = first.get(i) - second.get(i);
+ sum += diff * diff;
+ }
+ return Math.sqrt(sum);
+ }
+}
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/normalization/ModelSorter.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/normalization/ModelSorter.java
new file mode 100644
index 000000000..0594f77c1
--- /dev/null
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/normalization/ModelSorter.java
@@ -0,0 +1,53 @@
+package de.jplag.emf.normalization;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.resource.Resource;
+
+import de.jplag.emf.parser.ModelingElementTokenizer;
+import de.jplag.emf.util.AbstractMetamodelVisitor;
+
+/**
+ * Utility class that sorts all containment references of an EMF model or metamodel.
+ */
+public class ModelSorter extends AbstractMetamodelVisitor {
+
+ private final ModelingElementTokenizer tokenizer;
+
+ /**
+ * Creates a model sorter.
+ */
+ private ModelSorter(ModelingElementTokenizer tokenizer) {
+ this.tokenizer = tokenizer; // private constructor to hide visitor functionality.
+ }
+
+ /**
+ * Sorts the given model or metamodel.
+ * @param modelResource is the resource of the model or metamodel.
+ * @param tokenizer provides the tokenization rules for the sorting.
+ */
+ public static void sort(Resource modelResource, ModelingElementTokenizer tokenizer) {
+ modelResource.getContents().forEach(new ModelSorter(tokenizer)::visit);
+ }
+
+ @Override
+ protected void visitEObject(EObject eObject) {
+ for (EReference reference : eObject.eClass().getEAllContainments()) {
+ if (reference.isMany()) {
+ Object containment = eObject.eGet(reference);
+ if (containment instanceof List) {
+ @SuppressWarnings("unchecked") // There is no cleaner way
+ List containmentList = (List) containment;
+ List sortedContent = new ArrayList<>(containmentList);
+ sortedContent.sort(new ContainmentOrderNormalizer(sortedContent, tokenizer));
+ containmentList.clear();
+ containmentList.addAll(sortedContent);
+ }
+ }
+ }
+ }
+
+}
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/normalization/TokenVectorGenerator.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/normalization/TokenVectorGenerator.java
new file mode 100644
index 000000000..3eb4ce5ea
--- /dev/null
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/normalization/TokenVectorGenerator.java
@@ -0,0 +1,58 @@
+package de.jplag.emf.normalization;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.emf.ecore.EObject;
+
+import de.jplag.TokenType;
+import de.jplag.emf.MetamodelTokenType;
+import de.jplag.emf.parser.ModelingElementTokenizer;
+
+/**
+ * Utility class for the generation of token occurrence histograms for model subtrees.
+ */
+public class TokenVectorGenerator {
+
+ private final ModelingElementTokenizer tokenizer;
+
+ public TokenVectorGenerator(ModelingElementTokenizer tokenizer) {
+ this.tokenizer = tokenizer;
+ }
+
+ /**
+ * Generate a token occurrence vector for a subtree of a model.
+ * @param modelElements is a visitor for the subtree.
+ * @return a list, where each entry represents the number of tokens in the subtree. The order is determined by
+ * {@link MetamodelTokenType}.
+ */
+ public List generateOccurenceVector(Iterator modelElements) {
+ Map tokenTypeHistogram = new HashMap<>();
+
+ while (modelElements.hasNext()) {
+ tokenizer.element2OptionalToken(modelElements.next()).ifPresent(it -> tokenTypeHistogram.merge(it, 1, Integer::sum));
+ }
+ List occurenceVector = new ArrayList<>();
+ for (TokenType type : tokenizer.allTokenTypes()) {
+ occurenceVector.add(tokenTypeHistogram.getOrDefault(type, 0));
+ }
+ return normalize(occurenceVector);
+ }
+
+ public static List normalize(List vector) {
+ double magnitude = Math.sqrt(vector.stream().mapToInt(it -> it * it).sum());
+ if (magnitude == 0) {
+ return Collections.nCopies(vector.size(), 0.0);
+ }
+ List normalizedVector = new ArrayList<>();
+ for (int element : vector) {
+ double normalizedValue = element / magnitude;
+ normalizedVector.add(normalizedValue);
+ }
+ return normalizedVector;
+ }
+}
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/EcoreParser.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/EcoreParser.java
index 2f740eba3..c92b20a7d 100644
--- a/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/EcoreParser.java
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/EcoreParser.java
@@ -11,21 +11,22 @@
import de.jplag.AbstractParser;
import de.jplag.ParsingException;
import de.jplag.Token;
+import de.jplag.TokenType;
import de.jplag.emf.Language;
import de.jplag.emf.MetamodelToken;
-import de.jplag.emf.MetamodelTokenType;
+import de.jplag.emf.normalization.ModelSorter;
import de.jplag.emf.util.AbstractMetamodelVisitor;
+import de.jplag.emf.util.AbstractModelView;
import de.jplag.emf.util.EMFUtil;
import de.jplag.emf.util.EmfaticModelView;
/**
* Parser for EMF metamodels.
- * @author Timur Saglam
*/
public class EcoreParser extends AbstractParser {
protected List tokens;
protected File currentFile;
- protected EmfaticModelView treeView;
+ protected AbstractModelView treeView;
protected AbstractMetamodelVisitor visitor;
/**
@@ -58,16 +59,41 @@ protected void parseModelFile(File file) throws ParsingException {
if (model == null) {
throw new ParsingException(file, "failed to load model");
} else {
- treeView = new EmfaticModelView(file, model);
+ normalizeOrder(model);
+ treeView = createView(file, model);
+ visitor = createMetamodelVisitor();
for (EObject root : model.getContents()) {
- visitor = createMetamodelVisitor();
visitor.visit(root);
}
tokens.add(Token.fileEnd(currentFile));
- treeView.writeToFile(Language.VIEW_FILE_SUFFIX);
+ treeView.writeToFile(getCorrespondingViewFileSuffix());
}
}
+ /**
+ * @return the correct view file suffix for the model view. Can be overriden in subclasses for alternative views.
+ */
+ protected String getCorrespondingViewFileSuffix() {
+ return Language.VIEW_FILE_SUFFIX;
+ }
+
+ /**
+ * Creates a model view. Can be overriden in subclasses for alternative views.
+ * @param file is the path for the view file to be created.
+ * @param modelResource is the resource containing the metamodel.
+ * @return the view implementation.
+ */
+ protected AbstractModelView createView(File file, Resource modelResource) {
+ return new EmfaticModelView(file, modelResource);
+ }
+
+ /**
+ * Extension point for subclasses to employ different normalization.
+ */
+ protected void normalizeOrder(Resource modelResource) {
+ ModelSorter.sort(modelResource, new MetamodelElementTokenizer());
+ }
+
/**
* Extension point for subclasses to employ different token generators.
* @return a token generating metamodel visitor.
@@ -81,7 +107,7 @@ protected AbstractMetamodelVisitor createMetamodelVisitor() {
* @param type is the token type.
* @param source is the corresponding {@link EObject} for which the token is added.
*/
- void addToken(MetamodelTokenType type, EObject source) {
+ protected void addToken(TokenType type, EObject source) {
MetamodelToken token = new MetamodelToken(type, currentFile, source);
tokens.add(treeView.convertToMetadataEnrichedToken(token));
}
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/MetamodelElementTokenizer.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/MetamodelElementTokenizer.java
new file mode 100644
index 000000000..f5f7c66c1
--- /dev/null
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/MetamodelElementTokenizer.java
@@ -0,0 +1,114 @@
+package de.jplag.emf.parser;
+
+import java.util.Set;
+
+import org.eclipse.emf.ecore.EAnnotation;
+import org.eclipse.emf.ecore.EAttribute;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EDataType;
+import org.eclipse.emf.ecore.EEnum;
+import org.eclipse.emf.ecore.EEnumLiteral;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EOperation;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EParameter;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.ETypeParameter;
+import org.eclipse.emf.ecore.util.EcoreSwitch;
+
+import de.jplag.TokenType;
+import de.jplag.emf.MetamodelTokenType;
+
+/**
+ * Tokenizer for metamodel elements. Maps any {@link EObject} to a {@link MetamodelTokenType}.
+ */
+public class MetamodelElementTokenizer extends EcoreSwitch implements ModelingElementTokenizer {
+
+ @Override
+ public TokenType element2Token(EObject modelElement) {
+ return doSwitch(modelElement);
+ }
+
+ @Override
+ public MetamodelTokenType caseEAnnotation(EAnnotation eAnnotation) {
+ return MetamodelTokenType.ANNOTATION;
+ }
+
+ @Override
+ public MetamodelTokenType caseEAttribute(EAttribute eAttribute) {
+ if (eAttribute.isID()) {
+ return MetamodelTokenType.ID_ATTRIBUTE;
+ } else {
+ return MetamodelTokenType.ATTRIBUTE;
+ }
+ }
+
+ @Override
+ public MetamodelTokenType caseEClass(EClass eClass) {
+ if (eClass.isInterface()) {
+ return MetamodelTokenType.INTERFACE;
+ } else if (eClass.isAbstract()) {
+ return MetamodelTokenType.ABSTRACT_CLASS;
+ } else {
+ return MetamodelTokenType.CLASS;
+ }
+ }
+
+ @Override
+ public MetamodelTokenType caseEDataType(EDataType eDataType) {
+ return MetamodelTokenType.DATATYPE;
+ }
+
+ @Override
+ public MetamodelTokenType caseETypeParameter(ETypeParameter eTypeParameter) {
+ return MetamodelTokenType.TYPE_PARAMETER;
+ }
+
+ @Override
+ public MetamodelTokenType caseEParameter(EParameter eParameter) {
+ return MetamodelTokenType.PARAMETER;
+ }
+
+ @Override
+ public MetamodelTokenType caseEOperation(EOperation eOperation) {
+ return MetamodelTokenType.OPERATION;
+ }
+
+ @Override
+ public MetamodelTokenType caseEPackage(EPackage ePackage) {
+ return MetamodelTokenType.PACKAGE;
+ }
+
+ @Override
+ public MetamodelTokenType caseEEnumLiteral(EEnumLiteral eEnumLiteral) {
+ return MetamodelTokenType.ENUM_LITERAL;
+ }
+
+ @Override
+ public MetamodelTokenType caseEEnum(EEnum eEnum) {
+ return MetamodelTokenType.ENUM;
+ }
+
+ @Override
+ public MetamodelTokenType caseEReference(EReference eReference) {
+ if (eReference.isContainment()) {
+ if (eReference.getUpperBound() == 1) {
+ return MetamodelTokenType.CONTAINMENT;
+ } else {
+ return MetamodelTokenType.CONTAINMENT_MULT;
+ }
+ } else {
+ if (eReference.getUpperBound() == 1) {
+ return MetamodelTokenType.REFERENCE;
+ } else {
+ return MetamodelTokenType.REFERENCE_MULT;
+ }
+ }
+ }
+
+ @Override
+ public Set allTokenTypes() {
+ return Set.of(MetamodelTokenType.values());
+ }
+
+}
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/MetamodelTokenGenerator.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/MetamodelTokenGenerator.java
index 95b23c90d..c038c9068 100644
--- a/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/MetamodelTokenGenerator.java
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/MetamodelTokenGenerator.java
@@ -1,135 +1,56 @@
package de.jplag.emf.parser;
-import static de.jplag.emf.MetamodelTokenType.ABSTRACT_CLASS;
import static de.jplag.emf.MetamodelTokenType.ABSTRACT_CLASS_END;
-import static de.jplag.emf.MetamodelTokenType.ANNOTATION;
-import static de.jplag.emf.MetamodelTokenType.ATTRIBUTE;
import static de.jplag.emf.MetamodelTokenType.BOUND;
-import static de.jplag.emf.MetamodelTokenType.CLASS;
import static de.jplag.emf.MetamodelTokenType.CLASS_END;
-import static de.jplag.emf.MetamodelTokenType.CONTAINMENT;
-import static de.jplag.emf.MetamodelTokenType.DATATYPE;
-import static de.jplag.emf.MetamodelTokenType.ENUM;
import static de.jplag.emf.MetamodelTokenType.ENUM_END;
-import static de.jplag.emf.MetamodelTokenType.ENUM_LITERAL;
-import static de.jplag.emf.MetamodelTokenType.ID_ATTRIBUTE;
-import static de.jplag.emf.MetamodelTokenType.INTERFACE;
import static de.jplag.emf.MetamodelTokenType.INTERFACE_END;
-import static de.jplag.emf.MetamodelTokenType.OPERATION;
-import static de.jplag.emf.MetamodelTokenType.OPERATION_END;
-import static de.jplag.emf.MetamodelTokenType.PACKAGE;
import static de.jplag.emf.MetamodelTokenType.PACKAGE_END;
-import static de.jplag.emf.MetamodelTokenType.PARAMETER;
-import static de.jplag.emf.MetamodelTokenType.REFERENCE;
import static de.jplag.emf.MetamodelTokenType.RETURN_TYPE;
-import static de.jplag.emf.MetamodelTokenType.SUPER_TYPE;
import static de.jplag.emf.MetamodelTokenType.THROWS_DECLARATION;
-import static de.jplag.emf.MetamodelTokenType.TYPE_PARAMETER;
-import org.eclipse.emf.ecore.EAnnotation;
-import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
-import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
-import org.eclipse.emf.ecore.EEnumLiteral;
+import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
-import org.eclipse.emf.ecore.EParameter;
-import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.ETypeParameter;
import de.jplag.emf.util.AbstractMetamodelVisitor;
/**
- * Visits a metamodel containment tree and extracts the relevant token.
+ * Visits a metamodel containment tree and extracts the relevant token. See also {@link MetamodelElementTokenizer}.
* @author Timur Saglam
*/
public class MetamodelTokenGenerator extends AbstractMetamodelVisitor {
- private EcoreParser parser;
+ private final EcoreParser parser;
+ private final ModelingElementTokenizer tokenizer;
/**
* Creates the visitor.
* @param parser is the parser which receives the generated tokens.
*/
public MetamodelTokenGenerator(EcoreParser parser) {
- super(true);
this.parser = parser;
+ tokenizer = new MetamodelElementTokenizer();
}
@Override
- protected void visitEAnnotation(EAnnotation eAnnotation) {
- parser.addToken(ANNOTATION, eAnnotation);
- }
-
- @Override
- protected void visitEAttribute(EAttribute eAttribute) {
- if (eAttribute.isID()) {
- parser.addToken(ID_ATTRIBUTE, eAttribute);
- } else {
- parser.addToken(ATTRIBUTE, eAttribute);
- }
- }
-
- @Override
- protected void visitEClass(EClass eClass) {
- if (eClass.isInterface()) {
- parser.addToken(INTERFACE, eClass);
- } else if (eClass.isAbstract()) {
- parser.addToken(ABSTRACT_CLASS, eClass);
- } else {
- parser.addToken(CLASS, eClass);
- }
- eClass.getESuperTypes().forEach(it -> parser.addToken(SUPER_TYPE, eClass));
- }
-
- @Override
- protected void visitEDataType(EDataType eDataType) {
- if (!(eDataType instanceof EEnum)) {
- parser.addToken(DATATYPE, eDataType);
- }
- }
-
- @Override
- protected void visitEEnum(EEnum eEnum) {
- parser.addToken(ENUM, eEnum);
- }
-
- @Override
- protected void visitEEnumLiteral(EEnumLiteral eEnumLiteral) {
- parser.addToken(ENUM_LITERAL, eEnumLiteral);
+ protected void visitEObject(EObject eObject) {
+ // Create begin tokens for elements that directly map to a token.
+ tokenizer.element2OptionalToken(eObject).ifPresent(it -> parser.addToken(it, eObject));
}
@Override
protected void visitEOperation(EOperation eOperation) {
- parser.addToken(OPERATION, eOperation);
if (eOperation.getEType() != null) {
parser.addToken(RETURN_TYPE, eOperation);
}
eOperation.getEExceptions().forEach(it -> parser.addToken(THROWS_DECLARATION, it));
}
- @Override
- protected void visitEPackage(EPackage ePackage) {
- parser.addToken(PACKAGE, ePackage);
- }
-
- @Override
- protected void visitEParameter(EParameter eParameter) {
- parser.addToken(PARAMETER, eParameter);
- }
-
- @Override
- protected void visitEReference(EReference eReference) {
- if (eReference.isContainment()) {
- parser.addToken(CONTAINMENT, eReference);
- } else {
- parser.addToken(REFERENCE, eReference);
- }
- }
-
@Override
protected void visitETypeParameter(ETypeParameter eTypeParameter) {
- parser.addToken(TYPE_PARAMETER, eTypeParameter);
eTypeParameter.getEBounds().forEach(it -> parser.addToken(BOUND, it));
}
@@ -154,9 +75,4 @@ protected void leaveEEnum(EEnum eEnum) {
parser.addToken(ENUM_END, eEnum);
}
- @Override
- protected void leaveEOperation(EOperation eOperation) {
- parser.addToken(OPERATION_END, eOperation);
- }
-
}
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/ModelingElementTokenizer.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/ModelingElementTokenizer.java
new file mode 100644
index 000000000..bed7e55b3
--- /dev/null
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/parser/ModelingElementTokenizer.java
@@ -0,0 +1,48 @@
+package de.jplag.emf.parser;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import org.eclipse.emf.ecore.EObject;
+
+import de.jplag.TokenType;
+
+/**
+ * Tokenizer for EMF modeling elements. Maps any {@link EObject} to a {@link TokenType}.
+ */
+public interface ModelingElementTokenizer {
+
+ /**
+ * Returns the corresponding token type for a model element.
+ * @param modelElement is the model element.
+ * @return the token type or null if no token is extracted for that element.
+ */
+ TokenType element2Token(EObject modelElement);
+
+ /**
+ * Returns the corresponding the token types for a list of model elements. See
+ * {@link ModelingElementTokenizer#element2Token(EObject)}.
+ * @param modelElements contains the model elements.
+ * @return the list of corresponding token types, might contain less entries than elements.
+ */
+ default List elements2Tokens(List modelElements) {
+ return modelElements.stream().map(this::element2Token).filter(Objects::nonNull).toList();
+ }
+
+ /**
+ * Returns the corresponding token type for a model element. See
+ * {@link ModelingElementTokenizer#element2Token(EObject)}.
+ * @param modelElement is the model element.
+ * @return the optional token type.
+ */
+ default Optional element2OptionalToken(EObject modelElement) {
+ return Optional.ofNullable(element2Token(modelElement));
+ }
+
+ /**
+ * @return the set of all known token types.
+ */
+ Set allTokenTypes();
+}
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/util/AbstractMetamodelVisitor.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/util/AbstractMetamodelVisitor.java
index 2f15f4086..f957bd0dc 100644
--- a/languages/emf-metamodel/src/main/java/de/jplag/emf/util/AbstractMetamodelVisitor.java
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/util/AbstractMetamodelVisitor.java
@@ -1,7 +1,5 @@
package de.jplag.emf.util;
-import java.util.ArrayList;
-
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
@@ -26,12 +24,6 @@
*/
public abstract class AbstractMetamodelVisitor {
- private final boolean sortContainmentsByType;
-
- protected AbstractMetamodelVisitor(boolean sortContainmentsByType) {
- this.sortContainmentsByType = sortContainmentsByType;
- }
-
private int currentTreeDepth;
/**
@@ -48,6 +40,7 @@ public int getCurrentTreeDepth() {
* @param eObject is the EObject to visit.
*/
public final void visit(EObject eObject) {
+
visitEObject(eObject);
if (eObject instanceof EPackage ePackage) {
visitEPackage(ePackage);
@@ -98,13 +91,8 @@ public final void visit(EObject eObject) {
visitENamedElement(eNamedElement);
}
- var children = new ArrayList<>(eObject.eContents());
- if (sortContainmentsByType) {
- children.sort((first, second) -> first.eClass().getName().compareTo(second.eClass().getName()));
- }
-
currentTreeDepth++;
- for (EObject child : children) {
+ for (EObject child : eObject.eContents()) {
visit(child);
}
currentTreeDepth--;
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/util/AbstractModelView.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/util/AbstractModelView.java
index e0dcbf11b..d5261bc92 100644
--- a/languages/emf-metamodel/src/main/java/de/jplag/emf/util/AbstractModelView.java
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/util/AbstractModelView.java
@@ -8,22 +8,32 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import de.jplag.emf.MetamodelToken;
+
/**
* Textual representation of a model for the depiction of matches in submissions.
- * @author Timur Saglam
*/
-public class AbstractModelView {
+public abstract class AbstractModelView {
protected final File file;
protected final Logger logger;
protected final StringBuilder viewBuilder;
- public AbstractModelView(File file) {
+ protected AbstractModelView(File file) {
this.file = file;
logger = LoggerFactory.getLogger(this.getClass());
viewBuilder = new StringBuilder();
}
+ /**
+ * Creates a token with tracing information based on an existing one without. The token information may also be used to
+ * build up the model view. This means a model view may be only complete after passing every token to the view to
+ * enrich.
+ * @param token is the existing token without tracing information.
+ * @return the enriched token, with the tracing information corresponding to this view.
+ */
+ public abstract MetamodelToken convertToMetadataEnrichedToken(MetamodelToken token);
+
/**
* Writes the tree view into a file.
* @param suffix is the suffix of the file to be written.
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/util/EmfaticModelView.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/util/EmfaticModelView.java
index c2ae5c6ae..7f3445093 100644
--- a/languages/emf-metamodel/src/main/java/de/jplag/emf/util/EmfaticModelView.java
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/util/EmfaticModelView.java
@@ -15,6 +15,7 @@
import org.eclipse.emf.emfatic.core.generator.emfatic.Writer;
import de.jplag.Token;
+import de.jplag.TokenTrace;
import de.jplag.emf.MetamodelToken;
import de.jplag.emf.MetamodelTokenType;
@@ -22,7 +23,6 @@
* Textual view of an EMF metamodel based on Emfatic. Emfatic code is generated for the metamodel and the model elements
* are then traced to line in the code. The tracing is done via hashes as model element names and keyword detection via
* regex matching. The tracing is requires, as Emfatic does not provide it itself.
- * @author Timur Saglam
*/
public final class EmfaticModelView extends AbstractModelView {
// The following regular expressions match keywords of the Emfatic syntax:
@@ -37,7 +37,7 @@ public final class EmfaticModelView extends AbstractModelView {
private final List hashedLines; // code for model element tracing lookup
private final Map elementToLine; // maps model elements to Emfatic code line numbers
- private Copier modelCopier; // Allows to trace between original and copied elements
+ private final Copier modelCopier; // Allows to trace between original and copied elements
private int lastLineIndex; // last line given to a token
/**
@@ -62,6 +62,7 @@ public EmfaticModelView(File file, Resource modelResource) {
* @param token is the existing token without tracing information.
* @return the enriched token, with the tracing information corresponding to this view.
*/
+ @Override
public MetamodelToken convertToMetadataEnrichedToken(MetamodelToken token) {
int lineIndex = calculateLineIndexOf(token);
String line = lines.get(lineIndex);
@@ -72,7 +73,8 @@ public MetamodelToken convertToMetadataEnrichedToken(MetamodelToken token) {
lineIndex++;
columnIndex += columnIndex == Token.NO_VALUE ? 0 : 1;
- return new MetamodelToken(token.getType(), token.getFile(), lineIndex, columnIndex, length, token.getEObject());
+ TokenTrace trace = new TokenTrace(lineIndex, columnIndex, length);
+ return new MetamodelToken(token.getType(), token.getFile(), trace, token.getEObject());
}
/**
@@ -80,7 +82,7 @@ public MetamodelToken convertToMetadataEnrichedToken(MetamodelToken token) {
* elements in subsequently generated Emfatic code while avoiding name collisions.
*/
private final void replaceElementNamesWithHashes(Resource copiedResource) {
- AbstractMetamodelVisitor renamer = new AbstractMetamodelVisitor(false) {
+ AbstractMetamodelVisitor renamer = new AbstractMetamodelVisitor() {
@Override
protected void visitENamedElement(ENamedElement eNamedElement) {
eNamedElement.setName(Integer.toString(eNamedElement.hashCode()));
diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/util/MetamodelTreeView.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/util/MetamodelTreeView.java
index 36a736e01..55f3647bc 100644
--- a/languages/emf-metamodel/src/main/java/de/jplag/emf/util/MetamodelTreeView.java
+++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/util/MetamodelTreeView.java
@@ -1,75 +1,73 @@
package de.jplag.emf.util;
import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Optional;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
-import de.jplag.Token;
+import de.jplag.TokenTrace;
import de.jplag.emf.MetamodelToken;
/**
- * Simplistic tree view representation of an EMF metamodel.
- * @author Timur Saglam
+ * Very basic tree view representation of an EMF metamodel or model.
*/
public class MetamodelTreeView extends AbstractModelView {
-
- private int lineIndex;
- private int columnIndex;
-
- private static final String INDENTATION = " ";
+ private final List lines;
+ private final Map objectToLine;
/**
* Creates a tree view for a metamodel.
* @param file is the path to the metamodel.
*/
- public MetamodelTreeView(File file) {
+ public MetamodelTreeView(File file, Resource modelResource) {
super(file);
+ lines = new ArrayList<>();
+ objectToLine = new HashMap<>();
+ TreeViewBuilder visitor = new TreeViewBuilder();
+ modelResource.getContents().forEach(visitor::visit);
}
/**
* Adds a token to the view, thus adding the index information to the token. Returns a new token enriched with the index
* metadata.
* @param token is the token to add.
- * @param treeDepth is the current containment tree depth, required for the indentation.
*/
- public MetamodelToken convertToMetadataEnrichedTokenAndAdd(MetamodelToken token, int treeDepth, String prefix) {
- int length = Token.NO_VALUE;
- int line = Token.NO_VALUE;
- int column = Token.NO_VALUE;
+ @Override
+ public MetamodelToken convertToMetadataEnrichedToken(MetamodelToken token) {
Optional optionalEObject = token.getEObject();
if (optionalEObject.isPresent()) {
- EObject eObject = optionalEObject.get();
- if (prefix.isEmpty() && treeDepth > 0) {
- lineIndex++;
- columnIndex = 0;
- viewBuilder.append(System.lineSeparator());
- }
+ EObject object = optionalEObject.get();
+ TokenTrace trace = objectToLine.get(object);
+ return new MetamodelToken(token.getType(), token.getFile(), trace, optionalEObject);
+ }
+ return new MetamodelToken(token.getType(), token.getFile());
+ }
- String tokenText = token.getType().getDescription();
- if (eObject instanceof ENamedElement element) {
- tokenText = element.getName() + " : " + tokenText;
- }
- length = tokenText.length();
+ private final class TreeViewBuilder extends AbstractMetamodelVisitor {
+ private static final String INDENTATION = " ";
+ private static final String NAME_SEPARATOR = " : ";
- if (prefix.isEmpty()) {
- for (int i = 0; i < treeDepth; i++) {
- viewBuilder.append(INDENTATION);
- columnIndex += INDENTATION.length();
- }
- viewBuilder.append(tokenText);
- } else {
- viewBuilder.append(prefix + tokenText);
- columnIndex += prefix.length();
+ @Override
+ protected void visitEObject(EObject eObject) {
+ String prefix = INDENTATION.repeat(getCurrentTreeDepth());
+ String line = prefix;
+ if (eObject instanceof ENamedElement element) {
+ line += element.getName() + NAME_SEPARATOR;
}
+ line += eObject.eClass().getName();
- line = lineIndex + 1;
- column = columnIndex + 1;
-
- columnIndex += tokenText.length();
+ lines.add(line);
+ viewBuilder.append(line + System.lineSeparator());
+ // line and column values are one-indexed
+ TokenTrace trace = new TokenTrace(lines.size(), prefix.length() + 1, line.trim().length());
+ objectToLine.put(eObject, trace);
}
- return new MetamodelToken(token.getType(), token.getFile(), line, column, length, token.getEObject());
}
}
diff --git a/languages/emf-metamodel/src/test/java/de/jplag/emf/AbstractEmfTest.java b/languages/emf-metamodel/src/test/java/de/jplag/emf/AbstractEmfTest.java
index 0abb90def..0626026ab 100644
--- a/languages/emf-metamodel/src/test/java/de/jplag/emf/AbstractEmfTest.java
+++ b/languages/emf-metamodel/src/test/java/de/jplag/emf/AbstractEmfTest.java
@@ -1,11 +1,20 @@
package de.jplag.emf;
+import static org.junit.jupiter.api.Assertions.assertIterableEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
+import org.eclipse.emf.ecore.resource.Resource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import de.jplag.emf.util.EMFUtil;
import de.jplag.testutils.FileUtil;
/**
@@ -35,4 +44,34 @@ protected void setUp() {
protected void tearDown() {
FileUtil.clearFiles(new File(BASE_PATH.toString()), Language.VIEW_FILE_SUFFIX);
}
+
+ /**
+ * Load (meta)model from file and assert it is correctly loaded.
+ * @param modelFile is the file to load.
+ * @return the loaded resource.
+ */
+ protected Resource loadAndVerifyModel(File modelFile) {
+ assertTrue(modelFile.exists());
+ Resource modelResource = EMFUtil.loadModelResource(modelFile);
+ assertNotNull(modelResource);
+ return modelResource;
+ }
+
+ /**
+ * Compares the generated view file of a meta(model) with an expected one.
+ * @param modelFile is the file of the meta(model).
+ * @param viewFileSuffix is the suffix of the view file.
+ * @param directoryOfExpectedViews is the name of the folder where the expected view files are located.
+ */
+ protected void assertViewFilesMatch(File modelFile, String viewFileSuffix, String directoryOfExpectedViews) {
+ File viewFile = new File(modelFile.getPath() + viewFileSuffix);
+ File expectedViewFile = BASE_PATH.resolveSibling(Path.of(directoryOfExpectedViews, viewFile.getName())).toFile();
+ assertTrue(viewFile.exists());
+ assertTrue(expectedViewFile.exists());
+ try {
+ assertIterableEquals(Files.readAllLines(expectedViewFile.toPath()), Files.readAllLines(viewFile.toPath()));
+ } catch (IOException exception) {
+ fail(exception);
+ }
+ }
}
diff --git a/languages/emf-metamodel/src/test/java/de/jplag/emf/MinimalMetamodelTest.java b/languages/emf-metamodel/src/test/java/de/jplag/emf/MinimalMetamodelTest.java
index 7284803d3..8089d664c 100644
--- a/languages/emf-metamodel/src/test/java/de/jplag/emf/MinimalMetamodelTest.java
+++ b/languages/emf-metamodel/src/test/java/de/jplag/emf/MinimalMetamodelTest.java
@@ -33,8 +33,8 @@ void testBookstoreMetamodels() throws ParsingException {
logger.debug(TokenPrinter.printTokens(result, baseDirectory, Optional.of(Language.VIEW_FILE_SUFFIX)));
List tokenTypes = result.stream().map(Token::getType).toList();
logger.info("Parsed token types: " + tokenTypes.stream().map(TokenType::getDescription).toList().toString());
- assertEquals(82, tokenTypes.size());
- assertEquals(13, new HashSet<>(tokenTypes).size());
+ assertEquals(80, tokenTypes.size());
+ assertEquals(12, new HashSet<>(tokenTypes).size());
var originalTokens = TokenUtils.tokenTypesByFile(result, testFiles.get(0));
var renamedTokens = TokenUtils.tokenTypesByFile(result, testFiles.get(3));
diff --git a/languages/emf-metamodel/src/test/java/de/jplag/emf/util/EmfaticModelViewTest.java b/languages/emf-metamodel/src/test/java/de/jplag/emf/util/EmfaticModelViewTest.java
index d6f178c00..3c6d116c3 100644
--- a/languages/emf-metamodel/src/test/java/de/jplag/emf/util/EmfaticModelViewTest.java
+++ b/languages/emf-metamodel/src/test/java/de/jplag/emf/util/EmfaticModelViewTest.java
@@ -1,14 +1,6 @@
package de.jplag.emf.util;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
@@ -34,23 +26,13 @@ private static List provideModelNames() {
void testEmfaticViewFiles(String modelName) {
// Load model:
File modelFile = new File(baseDirectory, modelName);
- assertTrue(modelFile.exists());
- Resource modelResource = EMFUtil.loadModelResource(modelFile);
- assertNotNull(modelResource);
+ Resource modelResource = loadAndVerifyModel(modelFile);
// Generate emfatic view:
EmfaticModelView view = new EmfaticModelView(modelFile, modelResource);
view.writeToFile(Language.VIEW_FILE_SUFFIX);
// Compare expected vs. actual view file:
- File viewFile = new File(modelFile.getPath() + Language.VIEW_FILE_SUFFIX);
- File expectedViewFile = BASE_PATH.resolveSibling(Path.of(EXPECTED_VIEW_FOLDER, viewFile.getName())).toFile();
- assertTrue(viewFile.exists());
- assertTrue(expectedViewFile.exists());
- try {
- assertEquals(Files.readAllLines(expectedViewFile.toPath()), Files.readAllLines(viewFile.toPath()));
- } catch (IOException exception) {
- fail(exception);
- }
+ assertViewFilesMatch(modelFile, Language.VIEW_FILE_SUFFIX, EXPECTED_VIEW_FOLDER);
}
}
diff --git a/languages/emf-metamodel/src/test/java/de/jplag/emf/util/MetamodelTreeViewTest.java b/languages/emf-metamodel/src/test/java/de/jplag/emf/util/MetamodelTreeViewTest.java
new file mode 100644
index 000000000..9a2976974
--- /dev/null
+++ b/languages/emf-metamodel/src/test/java/de/jplag/emf/util/MetamodelTreeViewTest.java
@@ -0,0 +1,47 @@
+package de.jplag.emf.util;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.emf.ecore.resource.Resource;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import de.jplag.emf.AbstractEmfTest;
+import de.jplag.testutils.FileUtil;
+
+class MetamodelTreeViewTest extends AbstractEmfTest {
+
+ private static final String VIEW_FILE_SUFFIX = ".treeview";
+ private static final String EXPECTED_VIEW_FOLDER = "treeview";
+
+ private static List provideModelNames() {
+ return Arrays.asList(TEST_SUBJECTS);
+ }
+
+ @ParameterizedTest
+ @DisplayName("Test content of emfatic view files of example metamodels")
+ @MethodSource("provideModelNames")
+ void testEmfaticViewFiles(String modelName) {
+ // Load model:
+ File modelFile = new File(baseDirectory, modelName);
+ Resource modelResource = loadAndVerifyModel(modelFile);
+
+ // Generate emfatic view:
+ MetamodelTreeView view = new MetamodelTreeView(modelFile, modelResource);
+ view.writeToFile(VIEW_FILE_SUFFIX);
+
+ // Compare expected vs. actual view file:
+ assertViewFilesMatch(modelFile, VIEW_FILE_SUFFIX, EXPECTED_VIEW_FOLDER);
+ }
+
+ @AfterEach
+ @Override
+ protected void tearDown() {
+ FileUtil.clearFiles(new File(BASE_PATH.toString()), VIEW_FILE_SUFFIX);
+ }
+
+}
diff --git a/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStore.ecore.treeview b/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStore.ecore.treeview
new file mode 100644
index 000000000..ce6ad5331
--- /dev/null
+++ b/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStore.ecore.treeview
@@ -0,0 +1,13 @@
+BookStorePackage : EPackage
+ BookStore : EClass
+ owner : EAttribute
+ EGenericType
+ location : EAttribute
+ EGenericType
+ books : EReference
+ EGenericType
+ Book : EClass
+ name : EAttribute
+ EGenericType
+ isbn : EAttribute
+ EGenericType
diff --git a/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStoreExtended.ecore.treeview b/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStoreExtended.ecore.treeview
new file mode 100644
index 000000000..4fdd2c505
--- /dev/null
+++ b/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStoreExtended.ecore.treeview
@@ -0,0 +1,35 @@
+BookStorePackage : EPackage
+ store : EPackage
+ BookStore : EClass
+ owner : EReference
+ EGenericType
+ name : EAttribute
+ EGenericType
+ location : EAttribute
+ EGenericType
+ books : EReference
+ EGenericType
+ Book : EClass
+ name : EAttribute
+ EGenericType
+ isbn : EAttribute
+ EGenericType
+ author : EReference
+ EGenericType
+ genre : EAttribute
+ EGenericType
+ Genre : EEnum
+ NOVEL : EEnumLiteral
+ COOKBOOK : EEnumLiteral
+ BIOGRAPHY : EEnumLiteral
+ TEXTBOOK : EEnumLiteral
+ person : EPackage
+ Author : EClass
+ isStageName : EAttribute
+ EGenericType
+ EGenericType
+ Person : EClass
+ firstName : EAttribute
+ EGenericType
+ lastName : EAttribute
+ EGenericType
diff --git a/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStoreExtendedRefactor.ecore.treeview b/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStoreExtendedRefactor.ecore.treeview
new file mode 100644
index 000000000..ca127fe5a
--- /dev/null
+++ b/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStoreExtendedRefactor.ecore.treeview
@@ -0,0 +1,29 @@
+BookStorePackage : EPackage
+ store : EPackage
+ Store : EClass
+ owner : EReference
+ EGenericType
+ name : EAttribute
+ EGenericType
+ location : EAttribute
+ EGenericType
+ BookStore : EClass
+ books : EReference
+ EGenericType
+ EGenericType
+ Book : EClass
+ title : EAttribute
+ EGenericType
+ isbn : EAttribute
+ EGenericType
+ author : EReference
+ EGenericType
+ category : EAttribute
+ EGenericType
+ Person : EClass
+ firstName : EAttribute
+ EGenericType
+ lastName : EAttribute
+ EGenericType
+ isStageName : EAttribute
+ EGenericType
diff --git a/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStoreRenamed.ecore.treeview b/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStoreRenamed.ecore.treeview
new file mode 100644
index 000000000..4ee5941e3
--- /dev/null
+++ b/languages/emf-metamodel/src/test/resources/de/jplag/treeview/bookStoreRenamed.ecore.treeview
@@ -0,0 +1,13 @@
+BookStorePackage : EPackage
+ Store : EClass
+ nameOfOwner : EAttribute
+ EGenericType
+ city : EAttribute
+ EGenericType
+ soldItems : EReference
+ EGenericType
+ Item : EClass
+ title : EAttribute
+ EGenericType
+ identifier : EAttribute
+ EGenericType
diff --git a/languages/emf-model/pom.xml b/languages/emf-model/pom.xml
new file mode 100644
index 000000000..763a528c0
--- /dev/null
+++ b/languages/emf-model/pom.xml
@@ -0,0 +1,40 @@
+
+
+ 4.0.0
+
+ de.jplag
+ languages
+ ${revision}
+
+ emf-model
+
+
+
+ de.jplag
+ emf-metamodel-dynamic
+ ${revision}
+
+
+ de.jplag
+ language-testutils
+ ${revision}
+ test-jar
+ test
+
+
+ org.eclipse.emf
+ org.eclipse.emf.ecore
+ ${emf.version}
+
+
+ org.eclipse.emf
+ org.eclipse.emf.common
+ ${emf.ecore.version}
+
+
+ org.eclipse.emf
+ org.eclipse.emf.ecore.xmi
+ ${emf.ecore.xmi.version}
+
+
+
diff --git a/languages/emf-model/src/main/java/de/jplag/emf/model/Language.java b/languages/emf-model/src/main/java/de/jplag/emf/model/Language.java
new file mode 100644
index 000000000..092fb2af5
--- /dev/null
+++ b/languages/emf-model/src/main/java/de/jplag/emf/model/Language.java
@@ -0,0 +1,58 @@
+package de.jplag.emf.model;
+
+import java.io.File;
+import java.util.Comparator;
+import java.util.List;
+
+import org.kohsuke.MetaInfServices;
+
+import de.jplag.emf.model.parser.DynamicModelParser;
+
+/**
+ * Language for EMF metamodels from the Eclipse Modeling Framework (EMF). This language is based on a dynamically
+ * created token set.
+ * @author Timur Saglam
+ */
+@MetaInfServices(de.jplag.Language.class)
+public class Language extends de.jplag.emf.dynamic.Language {
+ private static final String NAME = "EMF models (dynamically created token set)";
+ private static final String IDENTIFIER = "emf-model";
+
+ public static final String VIEW_FILE_SUFFIX = ".treeview";
+
+ public Language() {
+ super(new DynamicModelParser());
+ }
+
+ @Override
+ public String[] suffixes() {
+ return new String[] {};
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ @Override
+ public String viewFileSuffix() {
+ return VIEW_FILE_SUFFIX;
+ }
+
+ @Override
+ public boolean expectsSubmissionOrder() {
+ return true;
+ }
+
+ @Override
+ public List customizeSubmissionOrder(List sub) {
+ Comparator fileEndingComparator = (first, second) -> Boolean.compare(second.getName().endsWith(FILE_ENDING),
+ first.getName().endsWith(FILE_ENDING));
+ return sub.stream().sorted(fileEndingComparator).toList();
+ }
+}
diff --git a/languages/emf-model/src/main/java/de/jplag/emf/model/parser/DynamicModelParser.java b/languages/emf-model/src/main/java/de/jplag/emf/model/parser/DynamicModelParser.java
new file mode 100644
index 000000000..4bc23cf72
--- /dev/null
+++ b/languages/emf-model/src/main/java/de/jplag/emf/model/parser/DynamicModelParser.java
@@ -0,0 +1,78 @@
+package de.jplag.emf.model.parser;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.resource.Resource;
+
+import de.jplag.ParsingException;
+import de.jplag.emf.dynamic.parser.DynamicEcoreParser;
+import de.jplag.emf.model.Language;
+import de.jplag.emf.util.AbstractModelView;
+import de.jplag.emf.util.EMFUtil;
+import de.jplag.emf.util.MetamodelTreeView;
+
+/**
+ * Parser for EMF metamodels based on dynamically created tokens.
+ */
+public class DynamicModelParser extends DynamicEcoreParser {
+ private static final String VIEW_FILE_WARNING = "Skipping view file {} as submission!";
+ private static final String METAPACKAGE_WARNING = "Loading model instance {} without any metamodel!";
+ private static final String METAPACKAGE_ERROR = "Error, not a metapackage: ";
+ private static final String METAMODEL_LOADING_ERROR = "Could not load metamodel file!";
+
+ private static final List metapackages = new ArrayList<>();
+ private static final String ALL_EXTENSIONS = "*";
+
+ /**
+ * Creates the parser.
+ */
+ public DynamicModelParser() {
+ EMFUtil.registerModelExtension(ALL_EXTENSIONS);
+ }
+
+ @Override
+ protected void parseModelFile(File file) throws ParsingException {
+ // implicit assumption: Metamodel gets parsed first!
+ if (file.getName().endsWith(de.jplag.emf.Language.FILE_ENDING)) {
+ parseMetamodelFile(file);
+ } else if (file.getName().endsWith(Language.VIEW_FILE_SUFFIX)) {
+ logger.warn(VIEW_FILE_WARNING, file.getName());
+ } else {
+ if (metapackages.isEmpty()) {
+ logger.warn(METAPACKAGE_WARNING, file.getName());
+ }
+ super.parseModelFile(file);
+ }
+ }
+
+ @Override
+ protected String getCorrespondingViewFileSuffix() {
+ return Language.VIEW_FILE_SUFFIX;
+ }
+
+ @Override
+ protected AbstractModelView createView(File file, Resource modelResource) {
+ return new MetamodelTreeView(file, modelResource);
+ }
+
+ private void parseMetamodelFile(File file) throws ParsingException {
+ metapackages.clear();
+ Resource modelResource = EMFUtil.loadModelResource(file);
+ if (modelResource == null) {
+ throw new ParsingException(file, METAMODEL_LOADING_ERROR);
+ } else {
+ for (EObject object : modelResource.getContents()) {
+ if (object instanceof EPackage ePackage) {
+ metapackages.add(ePackage);
+ } else {
+ logger.error(METAPACKAGE_ERROR, object);
+ }
+ }
+ EMFUtil.registerEPackageURIs(metapackages);
+ }
+ }
+}
diff --git a/languages/emf-model/src/test/java/de/jplag/emf/model/BookStoreFactory.java b/languages/emf-model/src/test/java/de/jplag/emf/model/BookStoreFactory.java
new file mode 100644
index 000000000..5f755defa
--- /dev/null
+++ b/languages/emf-model/src/test/java/de/jplag/emf/model/BookStoreFactory.java
@@ -0,0 +1,120 @@
+package de.jplag.emf.model;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EAttribute;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EFactory;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.ETypedElement;
+import org.eclipse.emf.ecore.EcoreFactory;
+import org.eclipse.emf.ecore.EcorePackage;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl;
+
+public class BookStoreFactory {
+
+ private static final String MODEL_NAME = "bookStore.xml";
+ private static final String METAMODEL_NAME = "bookStore.ecore";
+
+ public static void generateAll(String filePath) {
+ File file = new File(filePath);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ BookStoreFactory.createMetamodelAndModelInstance(filePath);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void createMetamodelAndModelInstance(String baseBath) {
+ /*
+ * Create metamodel:
+ */
+ EcoreFactory theCoreFactory = EcoreFactory.eINSTANCE;
+
+ EClass bookStoreEClass = theCoreFactory.createEClass();
+ bookStoreEClass.setName("BookStore");
+
+ EClass bookEClass = theCoreFactory.createEClass();
+ bookEClass.setName("Book");
+
+ EPackage bookStoreEPackage = theCoreFactory.createEPackage();
+ bookStoreEPackage.setName("BookStorePackage");
+ bookStoreEPackage.setNsPrefix("bookStore");
+ bookStoreEPackage.setNsURI("http:///com.ibm.dynamic.example.bookstore.ecore");
+
+ EcorePackage theCorePackage = EcorePackage.eINSTANCE;
+
+ EAttribute bookStoreOwner = theCoreFactory.createEAttribute();
+ bookStoreOwner.setName("owner");
+ bookStoreOwner.setEType(theCorePackage.getEString());
+ EAttribute bookStoreLocation = theCoreFactory.createEAttribute();
+ bookStoreLocation.setName("location");
+ bookStoreLocation.setEType(theCorePackage.getEString());
+ EReference bookStore_Books = theCoreFactory.createEReference();
+ bookStore_Books.setName("books");
+ bookStore_Books.setEType(bookEClass);
+ bookStore_Books.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
+ bookStore_Books.setContainment(true);
+
+ EAttribute bookName = theCoreFactory.createEAttribute();
+ bookName.setName("name");
+ bookName.setEType(theCorePackage.getEString());
+ EAttribute bookISBN = theCoreFactory.createEAttribute();
+ bookISBN.setName("isbn");
+ bookISBN.setID(true);
+ bookISBN.setEType(theCorePackage.getEInt());
+
+ bookStoreEClass.getEStructuralFeatures().add(bookStoreOwner);
+ bookStoreEClass.getEStructuralFeatures().add(bookStoreLocation);
+ bookStoreEClass.getEStructuralFeatures().add(bookStore_Books);
+
+ bookEClass.getEStructuralFeatures().add(bookName);
+ bookEClass.getEStructuralFeatures().add(bookISBN);
+
+ bookStoreEPackage.getEClassifiers().add(bookStoreEClass);
+ bookStoreEPackage.getEClassifiers().add(bookEClass);
+
+ /*
+ * Create model instance:
+ */
+ EFactory bookFactoryInstance = bookStoreEPackage.getEFactoryInstance();
+
+ EObject bookObject = bookFactoryInstance.create(bookEClass);
+ EObject bookStoreObject = bookFactoryInstance.create(bookStoreEClass);
+
+ bookStoreObject.eSet(bookStoreOwner, "David Brown");
+ bookStoreObject.eSet(bookStoreLocation, "Street#12, Top Town, NY");
+ ((List