Просмотр исходного кода

Add frontend for EMF model instances based on the dynamic metamodel frontend.

Timur Sağlam 4 лет назад
Родитель
Сommit
4fd47b5a95

+ 19 - 0
jplag.frontend-utils/src/main/java/de/jplag/Language.java

@@ -1,6 +1,7 @@
 package de.jplag;
 
 import java.io.File;
+import java.util.List;
 
 /**
  * Common interface for all languages. Each language-front end must provide a concrete language implementation.
@@ -77,4 +78,22 @@ public interface Language {
         return "";
     }
 
+    /**
+     * By default, most frontends do not care about the order of the submissions.
+     * @return true, if this frontends does care about the order of submissions.
+     */
+    default boolean expectsSubmissionOrder() {
+        return false;
+    }
+
+    /**
+     * Re-orders the submissions if {@link Language#expectsSubmissionOrder()} returns true.
+     * @param submissionNames are root files of the submissions, meaning a file for single file submissions or a directory
+     * for multi-file submissions.
+     * @return the re-ordered submission files.
+     */
+    default List<File> customizeSubmissionOrder(List<File> submissionNames) {
+        return submissionNames;
+    }
+
 }

+ 3 - 0
jplag.frontend-utils/src/main/java/de/jplag/TokenPrinter.java

@@ -6,6 +6,7 @@ import static java.util.stream.Collectors.toMap;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -150,6 +151,8 @@ public final class TokenPrinter {
     private static List<String> linesFromFile(File file) {
         try {
             return Files.readAllLines(file.toPath());
+        } catch (NoSuchFileException exception) {
+            logger.error("File does not exist, thus no tokens are printed: " + file.getAbsolutePath());
         } catch (IOException exception) {
             logger.error("Cannot read " + file.getAbsolutePath() + ":", exception);
         }

+ 15 - 3
jplag.frontend.emf-metamodel/src/main/java/de/jplag/emf/parser/EMFUtil.java

@@ -1,10 +1,12 @@
 package de.jplag.emf.parser;
 
+import java.io.File;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
 import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.common.util.WrappedException;
 import org.eclipse.emf.ecore.EObject;
 import org.eclipse.emf.ecore.EPackage;
 import org.eclipse.emf.ecore.EcorePackage;
@@ -12,6 +14,8 @@ 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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Utility class for EMF specific functionality.
@@ -19,6 +23,8 @@ import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
  */
 public final class EMFUtil {
 
+    private static final Logger logger = LoggerFactory.getLogger(EMFUtil.class.getSimpleName());
+
     private EMFUtil() {
         // private constructor for non-instantiability.
     }
@@ -52,12 +58,18 @@ public final class EMFUtil {
     /**
      * Loads a model or metamodel from a absolute file path.
      * @param filePath is the absolute path to the (meta)model.
-     * @return the content of the loaded (meta)model resFource.
+     * @return the content of the loaded (meta)model resource or null if it could not be loaded.
      */
     public static List<EObject> loadModel(String filePath) {
         final ResourceSet resourceSet = new ResourceSetImpl();
-        final Resource resource = resourceSet.getResource(URI.createFileURI(filePath), true);
-        return resource.getContents();
+        try {
+            final Resource resource = resourceSet.getResource(URI.createFileURI(filePath), true);
+            return resource.getContents();
+        } catch (WrappedException exception) {
+            String name = new File(filePath).getName();
+            logger.error("Could not load " + name + ": " + exception.getCause().getMessage());
+        }
+        return null;
     }
 
 }

+ 11 - 5
jplag.frontend.emf-metamodel/src/main/java/de/jplag/emf/parser/EcoreParser.java

@@ -38,6 +38,7 @@ public class EcoreParser extends AbstractParser {
      * @return the list of parsed tokens.
      */
     public TokenList parse(File directory, List<String> fileNames) {
+        errors = 0;
         tokens = new TokenList();
         for (String fileName : fileNames) {
             currentFile = fileName;
@@ -53,12 +54,17 @@ public class EcoreParser extends AbstractParser {
      */
     protected void parseModelFile(String filePath) {
         treeView = new MetamodelTreeView(filePath);
-        for (EObject root : EMFUtil.loadModel(filePath)) {
-            visitor = createMetamodelVisitor();
-            visitor.visit(root);
+        List<EObject> model = EMFUtil.loadModel(filePath);
+        if (model == null) {
+            errors++;
+        } else {
+            for (EObject root : model) {
+                visitor = createMetamodelVisitor();
+                visitor.visit(root);
+            }
+            tokens.addToken(new MetamodelToken(TokenConstants.FILE_END, currentFile + Language.VIEW_FILE_SUFFIX));
+            treeView.writeToFile(Language.VIEW_FILE_SUFFIX);
         }
-        tokens.addToken(new MetamodelToken(TokenConstants.FILE_END, currentFile + Language.VIEW_FILE_SUFFIX));
-        treeView.writeToFile(Language.VIEW_FILE_SUFFIX);
     }
 
     /**

+ 43 - 0
jplag.frontend.emf-model/pom.xml

@@ -0,0 +1,43 @@
+<?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-model</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>frontend-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>emf-metamodel-dynamic</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>

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

@@ -0,0 +1,49 @@
+package de.jplag.emf.model;
+
+import java.io.File;
+import java.util.List;
+
+import de.jplag.ErrorConsumer;
+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 instead of a hand-picked one.
+ * @author Timur Saglam
+ */
+public class Language extends de.jplag.emf.dynamic.Language {
+    private static final String NAME = "EMF models (dynamically created token set)";
+    private static final String SHORT_NAME = "EMF models (dynamic)";
+
+    public Language(ErrorConsumer program) {
+        super(new DynamicModelParser(program));
+    }
+
+    @Override
+    public String[] suffixes() {
+        return new String[] {};
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getShortName() {
+        return SHORT_NAME;
+    }
+
+    @Override
+    public boolean expectsSubmissionOrder() {
+        return true;
+    }
+
+    @Override
+    public List<File> customizeSubmissionOrder(List<File> submissionNames) {
+        submissionNames.sort((File first, File second) -> {
+            return Boolean.compare(first.getName().endsWith(FILE_ENDING), second.getName().endsWith(FILE_ENDING));
+        });
+        return submissionNames;
+    }
+}

+ 62 - 0
jplag.frontend.emf-model/src/main/java/de/jplag/emf/model/parser/DynamicModelParser.java

@@ -0,0 +1,62 @@
+package de.jplag.emf.model.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.jplag.ErrorConsumer;
+import de.jplag.emf.dynamic.parser.DynamicEcoreParser;
+import de.jplag.emf.parser.EMFUtil;
+
+/**
+ * Parser for EMF metamodels based on dynamically created tokens.
+ * @author Timur Saglam
+ */
+public class DynamicModelParser extends DynamicEcoreParser {
+    private static final Logger logger = LoggerFactory.getLogger(DynamicModelParser.class.getSimpleName());
+    private static final List<EPackage> metapackages = new ArrayList<>();
+    private static final String ALL_EXTENSIONS = "*";
+
+    /**
+     * Creates the parser.
+     * @param errorConsumer is the consumer for any occurring errors.
+     */
+    public DynamicModelParser(ErrorConsumer errorConsumer) {
+        super(errorConsumer);
+        EMFUtil.registerModelExtension(ALL_EXTENSIONS);
+    }
+
+    @Override
+    protected void parseModelFile(String filePath) {
+        // implicit assumption: Metamodel gets parsed first!
+        if (filePath.endsWith(de.jplag.emf.Language.FILE_ENDING)) {
+            parseMetamodelFile(filePath);
+        } else {
+            if (metapackages.isEmpty()) {
+                logger.warn("Loading model instances without any metamodel!");
+            }
+            super.parseModelFile(filePath);
+        }
+    }
+
+    private void parseMetamodelFile(String filePath) {
+        metapackages.clear();
+        List<EObject> model = EMFUtil.loadModel(filePath);
+        if (model == null) {
+            errors++;
+        } else {
+            for (EObject object : model) {
+                if (object instanceof EPackage ePackage) {
+                    metapackages.add(ePackage);
+                } else {
+                    logger.error("Error, not a metapackage: " + object);
+                }
+            }
+            EMFUtil.registerEPackageURIs(metapackages);
+        }
+    }
+}

+ 119 - 0
jplag.frontend.emf-model/src/test/java/de/jplag/emf/model/BookStoreFactory.java

@@ -0,0 +1,119 @@
+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.EStructuralFeature;
+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", "rawtypes"})
+    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(EStructuralFeature.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.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) bookStoreObject.eGet(bookStore_Books)).add(bookObject);
+
+        bookObject.eSet(bookName, "Harry Potter and the Deathly Hallows");
+        bookObject.eSet(bookISBN, 157221);
+
+        /*
+         * Save model instance and metamodel:
+         */
+        persist(baseBath, bookStoreObject, MODEL_NAME, "*");
+        persist(baseBath, bookStoreEPackage, METAMODEL_NAME, EcorePackage.eNAME);
+    }
+
+    private static void persist(String baseBath, EObject eObject, String name, String extension) {
+        ResourceSet metaResourceSet = new ResourceSetImpl();
+        metaResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(extension, new XMLResourceFactoryImpl());
+        Resource metaResource = metaResourceSet.createResource(URI.createFileURI(baseBath + File.separator + name));
+        metaResource.getContents().add(eObject);
+
+        try {
+            metaResource.save(null);
+        } catch (IOException exception) {
+            exception.printStackTrace();
+        }
+    }
+
+}

+ 62 - 0
jplag.frontend.emf-model/src/test/java/de/jplag/emf/model/ModelInstanceTest.java

@@ -0,0 +1,62 @@
+package de.jplag.emf.model;
+
+import static java.util.stream.Collectors.toList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.jplag.TokenList;
+import de.jplag.TokenPrinter;
+import de.jplag.emf.dynamic.DynamicMetamodelTokenConstants;
+import de.jplag.testutils.TestErrorConsumer;
+
+class ModelInstanceTest {
+    private final Logger logger = LoggerFactory.getLogger("JPlag-Test");
+
+    private static final Path BASE_PATH = Path.of("src", "test", "resources", "de", "jplag", "books");
+
+    private de.jplag.Language frontend;
+    private File baseDirectory;
+
+    @BeforeEach
+    public void setUp() {
+        TestErrorConsumer consumer = new TestErrorConsumer();
+        frontend = new Language(consumer);
+        baseDirectory = BASE_PATH.toFile();
+        assertTrue(baseDirectory.exists(), "Could not find base directory!");
+    }
+
+    @Test
+    void testParsingTestClass() {
+        File baseFile = new File(BASE_PATH.toString());
+        var testSubjects = Arrays.stream(baseFile.listFiles()).map(File::getName).filter(it -> !it.endsWith(Language.VIEW_FILE_SUFFIX))
+                .toArray(String[]::new);
+
+        TokenList result = frontend.parse(baseDirectory, testSubjects);
+        assertNotEquals(0, result.size());
+        List<String> treeViewFiles = Arrays.stream(testSubjects).map(it -> it + Language.VIEW_FILE_SUFFIX).collect(toList());
+        System.out.println(TokenPrinter.printTokens(result, baseDirectory, treeViewFiles));
+
+        logger.info("Dynamic token set: " + DynamicMetamodelTokenConstants.getTokenStrings());
+        logger.info(result.allTokens().toString());
+        assertEquals(6, result.size());
+    }
+
+    @AfterEach
+    public void tearDown() {
+        File baseFile = new File(BASE_PATH.toString());
+        Arrays.stream(baseFile.listFiles()).filter(it -> it.getName().endsWith(Language.VIEW_FILE_SUFFIX)).forEach(File::delete);
+    }
+
+}

+ 9 - 0
jplag/src/main/java/de/jplag/SubmissionSetBuilder.java

@@ -1,5 +1,7 @@
 package de.jplag;
 
+import static java.util.stream.Collectors.toList;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.*;
@@ -69,6 +71,13 @@ public class SubmissionSetBuilder {
 
         // Merge everything in a submission set.
         List<Submission> submissions = new ArrayList<>(foundSubmissions.values());
+
+        // Some languages expect a certain order, which is ensured here:
+        if (language.expectsSubmissionOrder()) {
+            List<File> rootFiles = foundSubmissions.values().stream().map(it -> it.getRoot()).collect(toList());
+            rootFiles = language.customizeSubmissionOrder(rootFiles);
+            submissions = new ArrayList<>(rootFiles.stream().map(it -> foundSubmissions.get(it)).collect(toList()));
+        }
         return new SubmissionSet(submissions, baseCodeSubmission, errorCollector, options);
     }
 

+ 2 - 1
jplag/src/main/java/de/jplag/options/LanguageOption.java

@@ -17,7 +17,8 @@ public enum LanguageOption {
     TEXT("text", "de.jplag.text.Language"),
     SCHEME("scheme", "de.jplag.scheme.Language"),
     EMF("emf-metamodel", "de.jplag.emf.Language"),
-    EMF_DYNAMIC("emf-metamodel-dynamic", "de.jplag.emf.dynamic.Language");
+    EMF_DYNAMIC("emf-metamodel-dynamic", "de.jplag.emf.dynamic.Language"),
+    EMF_MODEL("emf-model", "de.jplag.emf.model.Language");
 
     private final String classPath;
     private final String displayName;

+ 6 - 0
pom.xml

@@ -53,6 +53,7 @@
         <module>jplag.frontend.text</module>
         <module>jplag.frontend.emf-metamodel</module>
         <module>jplag.frontend.emf-metamodel-dynamic</module>
+        <module>jplag.frontend.emf-model</module>
         <module>jplag</module>
         <module>jplag.cli</module>
     </modules>
@@ -183,6 +184,11 @@
                 <artifactId>emf-metamodel-dynamic</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>emf-model</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>${project.groupId}</groupId>
                 <artifactId>frontend-utils</artifactId>