Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
* Mapping from source instances to target instances. Given that one source class can be the source for multiple
Expand All @@ -16,32 +17,35 @@ public class TargetMap {

private sealed interface Mapping {

Mapping with(EObject value);
Mapping join(Mapping other);

}

private record None() implements Mapping {

@Override
public Mapping with(EObject value) {
return new One(value);
public Mapping join(Mapping other) {
return other;
}

}

private record One(EObject value) implements Mapping {

@Override
public Mapping with(EObject value) {
return new Many();
public Mapping join(Mapping other) {
return switch (other) {
case None ignored -> this;
default -> new Many();
};
}

}

private record Many() implements Mapping {

@Override
public Mapping with(EObject value) {
public Mapping join(Mapping other) {
return this;
}

Expand All @@ -56,14 +60,22 @@ private Mapping getMapping(EObject source, AQRTargetClass targetClass) {
);
}

private Mapping getMappingInstanceOf(EObject source, AQRTargetClass targetClass) {
return map.entrySet().stream()
.filter(entry -> entry.getKey().left().equals(source))
.filter(entry -> entry.getKey().right().equals(targetClass) || entry.getKey().right().allSuperClasses().contains(targetClass))
.map(Entry::getValue)
.reduce(new None(), Mapping::join);
}

/**
* Register a new mapping from the source instance to the target instance via the target class.
*/
public void register(EObject source, AQRTargetClass targetClass, EObject target) {
var previous = getMapping(source, targetClass);
map.put(
new Pair<>(source, targetClass),
previous.with(target)
previous.join(new One(target))
);
}

Expand All @@ -73,14 +85,30 @@ public void register(EObject source, AQRTargetClass targetClass, EObject target)
* @throws TransformatorException if none or multiple target instances are mapped to the given source instance and target class
*/
public EObject get(EObject source, AQRTargetClass targetClass) {
return switch (getMapping(source, targetClass)) {
return get(source, targetClass, false);
}

/**
* Retrieve the target instance that is mapped to the given source instance and target class or subclasses or the target class.
*
* @throws TransformatorException if none or multiple target instances are mapped to the given source instance and target class
*/
public EObject get(EObject source, AQRTargetClass targetClass, boolean allowSubclasses) {
Mapping mapping;
if (allowSubclasses) {
mapping = getMappingInstanceOf(source, targetClass);
} else {
mapping = getMapping(source, targetClass);
}

return switch (mapping) {
case One(var value) -> value;
case None ignored -> throw new TransformatorException(
"no target instance of class '%s' found for source instance of class '%s'".formatted(
targetClass.name(), source.eClass().getName()
)
);
case Many ignored -> throw new TransformatorException(
"no target instance of class '%s' found for source instance of class '%s'".formatted(
targetClass.name(), source.eClass().getName()
)
);
case Many ignored -> throw new TransformatorException(
"multiple target instances of class '%s' found for source instance of class '%s'".formatted(
targetClass.name(), source.eClass().getName()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public EObject transform() throws TransformatorException {
// create other instances
aqr.classes().stream()
.filter(target -> target != aqr.root())
.filter(target -> !target.isAbstract())
.forEach(target -> {
var instances = transformTargetClass(target);
var rootRef = root.eClass()
Expand Down Expand Up @@ -306,9 +307,9 @@ private void populateReference(

private Object mapInstances(Object instance, AQRTargetClass target) {
if (instance instanceof List<?> list) {
return list.stream().map(i -> targetMap.get((EObject) i, target)).toList();
return list.stream().map(i -> targetMap.get((EObject) i, target, true)).toList();
} else {
return targetMap.get((EObject) instance, target);
return targetMap.get((EObject) instance, target, true);
}
}

Expand Down Expand Up @@ -342,6 +343,8 @@ private void checkNotAlreadyContained(EObject value, EObject target, AQRFeature.
} else if (featureKind instanceof AQRFeature.Kind.Copy.Implicit(EStructuralFeature feature)) {
check(source != null && source.eClass() == feature.getEContainingClass());
return source.eGet(feature);
} else if (featureKind instanceof AQRFeature.Kind.Override(AQRFeature overridden, AQRFeature.Kind overriding)) {
return evaluateFeature(overriding, source, context);
} else {
return fail();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

public class GenerateTest {

static List<String> validQueries = List.of("actor-rating", "books-on-tape", "customer-borrowings", "movies");
static List<String> validQueries = List.of("actor-rating", "books-on-tape", "customer-borrowings", "movies", "pizza2");

/**
* {@code java -jar cli.jar --meta-model-path=<meta-model-path> --generate=<output> <query>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

public class TransformTest {

static List<String> validQueries = List.of("pizza");
static List<String> validQueries = List.of("pizza", "pizza2");

@BeforeAll
public static void setupRegistry() {
Expand Down
29 changes: 29 additions & 0 deletions lang/frontend/cli/src/test/resources/models/pizza2.ecore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="pizza" nsURI="http://example.org/pizza2" nsPrefix="pizza">
<eClassifiers xsi:type="ecore:EEnum" name="FoodType">
<eLiterals name="FOOD"/>
<eLiterals name="DRINK" value="1"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Food" eSuperTypes="#//Item">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="price" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EFloat"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="type" eType="#//FoodType"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Item">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Root">
<eStructuralFeatures xsi:type="ecore:EReference" name="allFoods" ordered="false"
upperBound="-1" eType="#//Food" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="allItems" ordered="false"
upperBound="-1" eType="#//Item" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="allPizzerias" ordered="false"
upperBound="-1" eType="#//Pizzeria" containment="true"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Restaurant" abstract="true">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="sells" upperBound="-1"
eType="#//Item"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Pizzeria" eSuperTypes="#//Restaurant"/>
</ecore:EPackage>
21 changes: 21 additions & 0 deletions lang/frontend/cli/src/test/resources/queries/pizza2.nj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export pizza to "http://example.org/pizza2"

import "http://example.org/restaurant"

create abstract Restaurant {
name: EString
sells: Item [*]
}

create Item {
name := "<default>"
}
from Food
create extends Item

from Restaurant
where name.startsWith("Pizzeria")
create Pizzeria extends Restaurant {
name := name
sells := sells
}
29 changes: 29 additions & 0 deletions lang/frontend/cli/src/test/resources/results/pizza2.ecore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="pizza" nsURI="http://example.org/pizza2" nsPrefix="pizza">
<eClassifiers xsi:type="ecore:EEnum" name="FoodType">
<eLiterals name="FOOD"/>
<eLiterals name="DRINK" value="1"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Food" eSuperTypes="#//Item">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="price" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EFloat"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="type" eType="#//FoodType"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Item">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Root">
<eStructuralFeatures xsi:type="ecore:EReference" name="allFoods" ordered="false"
upperBound="-1" eType="#//Food" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="allItems" ordered="false"
upperBound="-1" eType="#//Item" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="allPizzerias" ordered="false"
upperBound="-1" eType="#//Pizzeria" containment="true"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Restaurant" abstract="true">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="sells" upperBound="-1"
eType="#//Item"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Pizzeria" eSuperTypes="#//Restaurant"/>
</ecore:EPackage>
8 changes: 8 additions & 0 deletions lang/frontend/cli/src/test/resources/results/pizza2.xmi
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="ASCII"?>
<pizza:Root xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:pizza="http://example.org/pizza2">
<allItems name="&lt;default>"/>
<allPizzerias name="Pizzeria Toni" sells="//@allFoods.0 //@allFoods.1"/>
<allFoods name="Pizza Margherita" price="7.0"/>
<allFoods name="Fanta" price="5.0" type="DRINK"/>
<allFoods name="Maultaschen" price="8.0"/>
</pizza:Root>
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ public void literal(String name) {
appendln(name);
}

public void clazz(String name, Runnable content) {
block("class " + name, content);
public void clazz(String name, boolean isAbstract, Runnable content) {
block((isAbstract ? "abstract " : "") + "class " + name, content);
}

public void attribute(String name, String type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public String generate() {
.forEach(classifier -> {
if (classifier instanceof EClass clazz) {
// class might have already been generated as super class of another class
clazzIfNew(clazz);
clazzIfNew(clazz, false);
} else if (classifier instanceof EEnum eEnum) {
// enum might have already been generated from a class attribute
enumerationIfNew(eEnum);
Expand All @@ -141,14 +141,16 @@ public String generate() {
// show selected target classes
selected.selectedClasses.stream()
.sorted(Comparator.comparing(EClassifier::getName))
.forEach(this::targetClazz);
// class might have already been generated as super class of another class
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, actually not, because that part is not implemented yet :p

.forEach(this::targetClazzIfNew);
} else {
// show all target classes
targetMetaModel.pack().getEClassifiers().stream()
.sorted(Comparator.comparing(EClassifier::getName))
.forEach(classifier -> {
if (classifier instanceof EClass clazz) {
targetClazz(clazz);
// class might have already been generated as super class of another class
targetClazzIfNew(clazz);
} else if (classifier instanceof EEnum eEnum) {
// enum might have already been generated from a class attribute
enumerationIfNew(eEnum);
Expand Down Expand Up @@ -195,14 +197,20 @@ private void packages() {
out.pack("\"Target: %s\" as %s".formatted(targetName, targetName), Empty);
}

private void targetClazzIfNew(EClass target) {
if (!seenClazzes.contains(target)) {
targetClazz(target);
}
}

private void targetClazz(EClass target) {
clazz(target);
clazz(target, true);

// show arrow from source to target classes
var sourceClasses = targetMetaModel.trace().getSourceClassesForTargetClass(target);
if (sourceClasses != null) {
sourceClasses.forEach(source -> {
clazzIfNew(source);
clazzIfNew(source, true);
out.arrow(
getQualifiedName(source),
ArrowSourceClass,
Expand All @@ -214,21 +222,21 @@ private void targetClazz(EClass target) {

private final HashSet<EClass> seenClazzes = new HashSet<>();

private void clazzIfNew(EClass clazz) {
private void clazzIfNew(EClass clazz, boolean target) {
if (!seenClazzes.contains(clazz)) {
clazz(clazz);
clazz(clazz, target);
}
}

private void clazz(EClass clazz) {
private void clazz(EClass clazz, boolean target) {
var isNew = seenClazzes.add(clazz);
check(isNew);

var qualifiedName = getQualifiedName(clazz);

// class with attributes
out.clazz(
qualifiedName, () -> {
qualifiedName, clazz.isAbstract(), () -> {
clazz.getEAttributes().forEach(attr -> {
var type = getQualifiedName(attr.getEType());
out.attribute(attr.getName(), type);
Expand All @@ -249,7 +257,11 @@ private void clazz(EClass clazz) {

// super types
for (var superType : clazz.getESuperTypes()) {
clazzIfNew(superType);
if (target) {
targetClazzIfNew(superType);
} else {
clazzIfNew(superType, false);
}
var superName = getQualifiedName(superType);
out.arrow(qualifiedName, PlantUMLBuilder.ReferenceInheritanceUpwards, superName);
}
Expand Down
Loading
Loading