Explorar o código

Add a prototypical EMF metamodel frontend.

Timur Sağlam %!s(int64=4) %!d(string=hai) anos
pai
achega
588494053a

+ 39 - 0
jplag.frontend.emf/pom.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>de.jplag</groupId>
+        <artifactId>aggregator</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <artifactId>emf</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>frontend-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>frontend-testutils</artifactId>
+            <version>${revision}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.emf</groupId>
+            <artifactId>org.eclipse.emf.ecore</artifactId>
+            <version>2.27.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.emf</groupId>
+            <artifactId>org.eclipse.emf.common</artifactId>
+            <version>2.25.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.emf</groupId>
+            <artifactId>org.eclipse.emf.ecore.xmi</artifactId>
+            <version>2.16.0</version>
+        </dependency>
+    </dependencies>
+</project>

+ 202 - 0
jplag.frontend.emf/src/main/java/de/jplag/emf/EcoreParser.java

@@ -0,0 +1,202 @@
+package de.jplag.emf;
+
+import static de.jplag.emf.MetamodelTokenConstants.ANNOTATION;
+import static de.jplag.emf.MetamodelTokenConstants.ATTRIBUTE;
+import static de.jplag.emf.MetamodelTokenConstants.CLASS;
+import static de.jplag.emf.MetamodelTokenConstants.DATATYPE;
+import static de.jplag.emf.MetamodelTokenConstants.ENUM;
+import static de.jplag.emf.MetamodelTokenConstants.ENUM_LITERAL;
+import static de.jplag.emf.MetamodelTokenConstants.INTERFACE;
+import static de.jplag.emf.MetamodelTokenConstants.OPERATION;
+import static de.jplag.emf.MetamodelTokenConstants.PACKAGE;
+import static de.jplag.emf.MetamodelTokenConstants.PARAMETER;
+import static de.jplag.emf.MetamodelTokenConstants.REFERENCE;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EAnnotation;
+import org.eclipse.emf.ecore.EAttribute;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EClassifier;
+import org.eclipse.emf.ecore.EDataType;
+import org.eclipse.emf.ecore.EEnum;
+import org.eclipse.emf.ecore.EEnumLiteral;
+import org.eclipse.emf.ecore.ENamedElement;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EOperation;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.EStructuralFeature;
+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.XMIResourceFactoryImpl;
+
+import de.jplag.AbstractParser;
+import de.jplag.ErrorConsumer;
+import de.jplag.TokenConstants;
+import de.jplag.TokenList;
+
+/**
+ * Parser for EMF metamodels.
+ * @author Timur Saglam
+ */
+public class EcoreParser extends AbstractParser {
+    private static final String TREE_VIEW_FILE_SUFFIX = ".tmp";
+    private TokenList tokens;
+    private String currentFile;
+    private StringBuilder treeView;
+    private int lineIndex;
+    private int columnIndex;
+
+    /**
+     * Creates the parser.
+     * @param errorConsumer is the consumer for any occurring errors.
+     */
+    public EcoreParser(ErrorConsumer errorConsumer) {
+        super(errorConsumer);
+    }
+
+    /**
+     * Parses all tokens form a list of files.
+     * @param directory is the base directory.
+     * @param fileNames is the list of file names.
+     * @return the list of parsed tokens.
+     */
+    public TokenList parse(File directory, List<String> fileNames) {
+        tokens = new TokenList();
+        errors = 0;
+        for (String fileName : fileNames) {
+            EPackage root = loadModel(directory.toString(), fileName);
+            currentFile = fileName + TREE_VIEW_FILE_SUFFIX;
+            treeView = new StringBuilder();
+            lineIndex = 0;
+            columnIndex = 0;
+            parseEPackage(root);
+
+            tokens.addToken(new MetamodelToken(TokenConstants.FILE_END, fileName, -1, -1, -1));
+            createTreeViewFile(directory, fileName);
+        }
+        return tokens;
+    }
+
+    private void createTreeViewFile(File directory, String fileName) {
+        File treeViewFile = new File(directory, currentFile);
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(treeViewFile));) {
+            if (!treeViewFile.exists()) {
+                treeViewFile.createNewFile();
+            }
+            writer.append(treeView.toString());
+        } catch (IOException exception) {
+            errors++;
+            getErrorConsumer().addError("Parsing Error in '" + fileName + "':" + exception.toString());
+            exception.printStackTrace();
+        }
+    }
+
+    private EPackage loadModel(String directory, String name) {
+        EcorePackage.eINSTANCE.eClass();
+        final Resource.Factory.Registry registry = Resource.Factory.Registry.INSTANCE;
+        final Map<String, Object> extensionMap = registry.getExtensionToFactoryMap();
+        extensionMap.put(EcorePackage.eNAME, new XMIResourceFactoryImpl());
+        final ResourceSet resourceSet = new ResourceSetImpl();
+        Resource resource = resourceSet.getResource(URI.createFileURI(directory + File.separator + name), true);
+        EObject content = resource.getContents().get(0);
+        if (content instanceof EPackage root) {
+            return root;
+        }
+        throw new IllegalStateException("Root element of model " + name + " is not a package: " + content);
+    }
+
+    private void parseEPackage(EPackage ePackage) {
+        addToken(PACKAGE, ePackage, true);
+        ePackage.getEClassifiers().forEach(it -> parseEClassifier(it));
+        ePackage.getEAnnotations().forEach(it -> parseEAnnotation(it));
+        ePackage.getESubpackages().forEach(it -> parseEPackage(it));
+    }
+
+    private void parseEAnnotation(EAnnotation annotation) {
+        addToken(ANNOTATION, annotation, true);
+    }
+
+    private void parseEClassifier(EClassifier classifier) {
+        if (classifier instanceof EClass eClass) {
+            parseEClass(eClass);
+        } else if (classifier instanceof EDataType dataType) {
+            parseEDataType(dataType);
+        }
+        // TODO TS: Parse type parameters & generic type?
+        classifier.getEAnnotations().forEach(it -> parseEAnnotation(it));
+    }
+
+    private void parseEClass(EClass eClass) {
+        if (eClass.isInterface()) {
+            addToken(INTERFACE, eClass, true);
+        } else {
+            addToken(CLASS, eClass, true);
+        }
+        // TODO TS: Extract abstract? probably not
+        // TODO TS: Extract super type relation? probably
+        eClass.getEStructuralFeatures().forEach(it -> parseEStructuralFeature(it));
+        eClass.getEOperations().forEach(it -> parseEOperation(it));
+    }
+
+    private void parseEOperation(EOperation operation) {
+        addToken(OPERATION, operation, true);
+        operation.getEParameters().forEach(it -> addToken(PARAMETER, it, false));
+        // TODO TS: Extract return type?
+        // TODO TS: Extract throws declarations?
+        // TODO TS: Parse type parameters & generic type?
+        operation.getEAnnotations().forEach(it -> parseEAnnotation(it));
+    }
+
+    private void parseEStructuralFeature(EStructuralFeature structuralFeature) {
+        if (structuralFeature instanceof EAttribute attribute) {
+            addToken(ATTRIBUTE, attribute, true);
+        } else if (structuralFeature instanceof EReference reference) {
+            addToken(REFERENCE, reference, true);
+            // TODO TS: Extract containment?
+        }
+        structuralFeature.getEAnnotations().forEach(it -> parseEAnnotation(it));
+    }
+
+    private void parseEDataType(EDataType dataType) {
+        if (dataType instanceof EEnum enumeration) {
+            parseEEnum(enumeration);
+        } else {
+            addToken(DATATYPE, dataType, true);
+        }
+    }
+
+    private void parseEEnum(EEnum enumeration) {
+        addToken(ENUM, enumeration, true);
+        enumeration.getELiterals().forEach(it -> parseEEnumLiteral(it));
+    }
+
+    private void parseEEnumLiteral(EEnumLiteral enumeral) {
+        addToken(ENUM_LITERAL, enumeral, false);
+        enumeral.getEAnnotations().forEach(it -> parseEAnnotation(it));
+    }
+
+    private void addToken(int type, EObject source, boolean newLine) {
+        if (newLine) {
+            lineIndex++;
+            treeView.append(System.lineSeparator());
+        }
+        String tokenText = source.getClass().getSimpleName().toString() + " ";
+        if (source instanceof ENamedElement element) {
+            tokenText += " " + element.getName();
+        }
+        int tokenLength = tokenText.length();
+        treeView.append(tokenText);
+        tokens.addToken(new MetamodelToken(type, currentFile, lineIndex, columnIndex, tokenLength));
+        columnIndex += tokenLength;
+    }
+}

+ 74 - 0
jplag.frontend.emf/src/main/java/de/jplag/emf/Language.java

@@ -0,0 +1,74 @@
+package de.jplag.emf;
+
+import java.io.File;
+import java.util.Arrays;
+
+import de.jplag.ErrorConsumer;
+import de.jplag.TokenList;
+
+/**
+ * Language for Ecore metamodels from the Eclipse Modeling Framework (EMF).
+ * @author Timur Saglam
+ */
+public class Language implements de.jplag.Language {
+    private static final String NAME = "EMF metamodels";
+    private static final String SHORT_NAME = "EMF";
+    private static final String[] FILE_ENDINGS = new String[] {".ecore"};
+    private static final int DEFAULT_MIN_TOKEN_MATCH = 5;
+
+    private final EcoreParser parser;
+
+    public Language(ErrorConsumer program) {
+        parser = new EcoreParser(program);
+    }
+
+    @Override
+    public String[] suffixes() {
+        return FILE_ENDINGS;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getShortName() {
+        return SHORT_NAME;
+    }
+
+    @Override
+    public int minimumTokenMatch() {
+        return DEFAULT_MIN_TOKEN_MATCH;
+    }
+
+    @Override
+    public TokenList parse(File dir, String[] files) {
+        return parser.parse(dir, Arrays.asList(files));
+    }
+
+    @Override
+    public boolean hasErrors() {
+        return parser.hasErrors();
+    }
+
+    @Override
+    public boolean supportsColumns() {
+        return true;
+    }
+
+    @Override
+    public boolean isPreformatted() {
+        return true;
+    }
+
+    @Override
+    public boolean usesIndex() {
+        return false;
+    }
+
+    @Override
+    public int numberOfTokens() {
+        return MetamodelTokenConstants.NUM_DIFF_TOKENS;
+    }
+}

+ 42 - 0
jplag.frontend.emf/src/main/java/de/jplag/emf/MetamodelToken.java

@@ -0,0 +1,42 @@
+package de.jplag.emf;
+
+import de.jplag.Token;
+import de.jplag.TokenConstants;
+
+/**
+ * Ecore metamodel token class.
+ * @author Timur Saglam
+ */
+public class MetamodelToken extends Token implements MetamodelTokenConstants {
+
+    /**
+     * Creates an Ecore metamodel token.
+     * @param type is the corresponding ID of the {@link TokenConstants}.
+     * @param file is the name of the source model file.
+     * @param line is the line index in the metamodel 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 metamodel.
+     */
+    public MetamodelToken(int type, String file, int line, int column, int length) {
+        super(type, file, line, column, length);
+    }
+
+    @Override
+    protected String type2string() {
+        return switch (type) {
+            case PACKAGE -> "EPackage";
+            case ANNOTATION -> "EAnnotation";
+            case CLASS -> "EClass";
+            case DATATYPE -> "EDatatype";
+            case ENUM -> "EEnum";
+            case ENUM_LITERAL -> "EEnumLiteral";
+            case OPERATION -> "EOperation";
+            case REFERENCE -> "EReference";
+            case ATTRIBUTE -> "EAttribute";
+            case PARAMETER -> "EParameter";
+            case INTERFACE -> "EInterface";
+            case FILE_END -> "(EOF)";
+            default -> "<UNKNOWN" + type + ">";
+        };
+    }
+}

+ 26 - 0
jplag.frontend.emf/src/main/java/de/jplag/emf/MetamodelTokenConstants.java

@@ -0,0 +1,26 @@
+package de.jplag.emf;
+
+import de.jplag.TokenConstants;
+
+/**
+ * @author Timur Saglam
+ */
+public interface MetamodelTokenConstants extends TokenConstants {
+
+    int PACKAGE = 2;
+    int ANNOTATION = 3;
+    int CLASS = 4;
+    int DATATYPE = 5;
+    int ENUM = 6;
+    int ENUM_LITERAL = 7;
+    int OPERATION = 8;
+    int REFERENCE = 9;
+    int ATTRIBUTE = 10;
+    int PARAMETER = 11;
+    int INTERFACE = 12;
+
+    /*
+     * Number of token constants:
+     */
+    int NUM_DIFF_TOKENS = 13;
+}

+ 6 - 0
pom.xml

@@ -51,6 +51,7 @@
         <module>jplag.frontend.python-3</module>
         <module>jplag.frontend.scheme</module>
         <module>jplag.frontend.text</module>
+        <module>jplag.frontend.emf</module>
         <module>jplag</module>
         <module>jplag.cli</module>
     </modules>
@@ -171,6 +172,11 @@
                 <artifactId>chars</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>emf</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>${project.groupId}</groupId>
                 <artifactId>frontend-utils</artifactId>