Skip to content
Snippets Groups Projects
Verified Commit 2973d213 authored by Rico Bergmann's avatar Rico Bergmann
Browse files

[WIP] Extend ModelJoin writer to write to FS

A ton of JavaDoc is still missing
parent 2ab5b275
Branches
No related tags found
No related merge requests found
Showing
with 285 additions and 101 deletions
...@@ -34,7 +34,7 @@ lazy val generator = (project in file(".")) ...@@ -34,7 +34,7 @@ lazy val generator = (project in file("."))
"org.junit.vintage" % "junit-vintage-engine" % "4.12.0" % "test", "org.junit.vintage" % "junit-vintage-engine" % "4.12.0" % "test",
"org.assertj" % "assertj-core" % "3.12.2" % "test", "org.assertj" % "assertj-core" % "3.12.2" % "test",
"com.novocode" % "junit-interface" % "0.11" % "test" "net.aichler" % "jupiter-interface" % JupiterKeys.jupiterVersion.value % Test
), ),
scalacOptions ++= Seq( scalacOptions ++= Seq(
"-language:implicitConversions" "-language:implicitConversions"
......
resolvers += Resolver.jcenterRepo
addSbtPlugin("net.aichler" % "sbt-jupiter-interface" % "0.8.2")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0")
package org.rosi_project.model_sync.model_join.representation.grammar;
import java.util.List;
import javax.annotation.Nonnull;
/**
* A {@code CompoundKeepExpression} is a special kind of {@code KeepExpression} which in turn may
* contain a number of other {@code KeepExpression}s.
*
* @author Rico Bergmann
*/
public abstract class CompoundKeepExpression extends KeepExpression {
/**
* Provides all the keep expressions that this expression is build of.
*/
@Nonnull
public abstract List<KeepExpression> getKeeps();
}
...@@ -28,7 +28,7 @@ import org.rosi_project.model_sync.model_join.representation.util.Assert; ...@@ -28,7 +28,7 @@ import org.rosi_project.model_sync.model_join.representation.util.Assert;
* *
* @author Rico Bergmann * @author Rico Bergmann
*/ */
public class KeepReferenceExpression extends KeepExpression { public class KeepReferenceExpression extends CompoundKeepExpression {
/** /**
* The {@code ReferenceDirection} defines whether a reference is owned by the containing model * The {@code ReferenceDirection} defines whether a reference is owned by the containing model
...@@ -188,6 +188,7 @@ public class KeepReferenceExpression extends KeepExpression { ...@@ -188,6 +188,7 @@ public class KeepReferenceExpression extends KeepExpression {
* the referenced instances. * the referenced instances.
*/ */
@Nonnull @Nonnull
@Override
public List<KeepExpression> getKeeps() { public List<KeepExpression> getKeeps() {
return new ArrayList<>(keeps); return new ArrayList<>(keeps);
} }
......
...@@ -17,7 +17,7 @@ import org.rosi_project.model_sync.model_join.representation.util.Assert; ...@@ -17,7 +17,7 @@ import org.rosi_project.model_sync.model_join.representation.util.Assert;
* *
* @author Rico Bergmann * @author Rico Bergmann
*/ */
public class KeepSubTypeExpression extends KeepExpression { public class KeepSubTypeExpression extends CompoundKeepExpression {
/** /**
* The {@code KeepSubTypeBuilder} enables the construction of new {@code KeepSubTypeExpression} * The {@code KeepSubTypeBuilder} enables the construction of new {@code KeepSubTypeExpression}
...@@ -132,6 +132,7 @@ public class KeepSubTypeExpression extends KeepExpression { ...@@ -132,6 +132,7 @@ public class KeepSubTypeExpression extends KeepExpression {
* the instances of the subclass instances. * the instances of the subclass instances.
*/ */
@Nonnull @Nonnull
@Override
public List<KeepExpression> getKeeps() { public List<KeepExpression> getKeeps() {
return new ArrayList<>(keeps); return new ArrayList<>(keeps);
} }
......
...@@ -16,7 +16,7 @@ import org.rosi_project.model_sync.model_join.representation.util.Assert; ...@@ -16,7 +16,7 @@ import org.rosi_project.model_sync.model_join.representation.util.Assert;
* *
* @author Rico Bergmann * @author Rico Bergmann
*/ */
public class KeepSuperTypeExpression extends KeepExpression { public class KeepSuperTypeExpression extends CompoundKeepExpression {
/** /**
* The {@code KeepSuperTypeBuilder} enables the construction of new {@code KeepSuperTypeExpression} * The {@code KeepSuperTypeBuilder} enables the construction of new {@code KeepSuperTypeExpression}
...@@ -131,6 +131,7 @@ public class KeepSuperTypeExpression extends KeepExpression { ...@@ -131,6 +131,7 @@ public class KeepSuperTypeExpression extends KeepExpression {
* the instances of the superclass instances. * the instances of the superclass instances.
*/ */
@Nonnull @Nonnull
@Override
public List<KeepExpression> getKeeps() { public List<KeepExpression> getKeeps() {
return new ArrayList<>(keeps); return new ArrayList<>(keeps);
} }
......
package org.rosi_project.model_sync.model_join.representation.writer;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression;
class FileBasedModelJoinWriter implements ModelJoinWriter<Boolean> {
private final File outputFile;
private final StringBasedModelJoinWriter toStringWriter;
FileBasedModelJoinWriter(File outputFile) {
this.outputFile = outputFile;
this.toStringWriter = StringBasedModelJoinWriter.withNewlines();
}
@Override
public Boolean write(ModelJoinExpression modelJoin) {
try (BufferedWriter writer = Files.newBufferedWriter(
outputFile.toPath(),
StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
String modelJoinTextRepresentation = toStringWriter.write(modelJoin);
writer.write(modelJoinTextRepresentation);
return true;
} catch (IOException ioe) {
return false;
}
}
}
package org.rosi_project.model_sync.model_join.representation.writer;
import java.io.File;
import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression;
public class ModelJoinWriterFactory {
public static String writeToString(ModelJoinExpression modelJoin) {
return StringBasedModelJoinWriter.createDefault().write(modelJoin);
}
public static boolean writeToFile(File outputFile, ModelJoinExpression modelJoin) {
return new FileBasedModelJoinWriter(outputFile).write(modelJoin);
}
}
...@@ -2,6 +2,7 @@ package org.rosi_project.model_sync.model_join.representation.writer; ...@@ -2,6 +2,7 @@ package org.rosi_project.model_sync.model_join.representation.writer;
import java.util.StringJoiner; import java.util.StringJoiner;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.rosi_project.model_sync.model_join.representation.grammar.CompoundKeepExpression;
import org.rosi_project.model_sync.model_join.representation.grammar.JoinExpression; import org.rosi_project.model_sync.model_join.representation.grammar.JoinExpression;
import org.rosi_project.model_sync.model_join.representation.grammar.KeepAggregateExpression; import org.rosi_project.model_sync.model_join.representation.grammar.KeepAggregateExpression;
import org.rosi_project.model_sync.model_join.representation.grammar.KeepAttributesExpression; import org.rosi_project.model_sync.model_join.representation.grammar.KeepAttributesExpression;
...@@ -17,11 +18,37 @@ import org.rosi_project.model_sync.model_join.representation.grammar.ThetaJoinEx ...@@ -17,11 +18,37 @@ import org.rosi_project.model_sync.model_join.representation.grammar.ThetaJoinEx
import org.rosi_project.model_sync.model_join.representation.util.Functional; import org.rosi_project.model_sync.model_join.representation.util.Functional;
import org.rosi_project.model_sync.model_join.representation.util.Nothing; import org.rosi_project.model_sync.model_join.representation.util.Nothing;
public class StringBasedModelJoinWriter implements ModelJoinWriter<String> { class StringBasedModelJoinWriter implements ModelJoinWriter<String> {
private static final String NEWLINE_DELIM = "\n";
private static final String EMPTY_DELIM = " ";
private static final String DEFAULT_INDENTATION = " ";
public static StringBasedModelJoinWriter createDefault() {
return new StringBasedModelJoinWriter(false);
}
public static StringBasedModelJoinWriter withNewlines() {
return new StringBasedModelJoinWriter(true);
}
private final String delimiter;
private final boolean useNewlineDelimiters;
private String currentIndentation = "";
private StringBasedModelJoinWriter(boolean useNewlinesDelimiters) {
if (useNewlinesDelimiters) {
this.delimiter = NEWLINE_DELIM;
} else {
this.delimiter = EMPTY_DELIM;
}
this.useNewlineDelimiters = useNewlinesDelimiters;
}
@Override @Override
public String write(@Nonnull ModelJoinExpression modelJoin) { public String write(@Nonnull ModelJoinExpression modelJoin) {
StringJoiner joiner = new StringJoiner("\n\n"); StringJoiner joiner = new StringJoiner(delimiter + delimiter);
modelJoin.getJoins().forEach( modelJoin.getJoins().forEach(
joinStatement -> joiner.merge(writeJoin(joinStatement)) joinStatement -> joiner.merge(writeJoin(joinStatement))
...@@ -47,7 +74,7 @@ public class StringBasedModelJoinWriter implements ModelJoinWriter<String> { ...@@ -47,7 +74,7 @@ public class StringBasedModelJoinWriter implements ModelJoinWriter<String> {
} }
}) })
.runOrThrow(); .runOrThrow();
joiner.add(String.format("%s %s with %s as %s", joinHeader, join.getLeft(), join.getRight(), join.getTarget())); joiner.add(String.format(currentIndentation + "%s %s with %s as %s", joinHeader, join.getLeft(), join.getRight(), join.getTarget()));
Functional.<Nothing>match(join) Functional.<Nothing>match(join)
.caseOf(ThetaJoinExpression.class, thetaJoin -> { .caseOf(ThetaJoinExpression.class, thetaJoin -> {
joiner.add("where").add(thetaJoin.getCondition().toString()); joiner.add("where").add(thetaJoin.getCondition().toString());
...@@ -57,8 +84,10 @@ public class StringBasedModelJoinWriter implements ModelJoinWriter<String> { ...@@ -57,8 +84,10 @@ public class StringBasedModelJoinWriter implements ModelJoinWriter<String> {
.runOrThrow(); .runOrThrow();
joiner.add("{\n"); joiner.add("{\n");
join.getKeeps().forEach(keep -> joiner.add(writeKeep(keep) + "\n")); increaseIndentationIfNecessary();
joiner.add("}"); join.getKeeps().forEach(keep -> joiner.add(currentIndentation + writeKeep(keep) + delimiter));
decreaseIndentationIfNecessary();
joiner.add(currentIndentation + "}");
return joiner; return joiner;
} }
...@@ -81,47 +110,45 @@ public class StringBasedModelJoinWriter implements ModelJoinWriter<String> { ...@@ -81,47 +110,45 @@ public class StringBasedModelJoinWriter implements ModelJoinWriter<String> {
keepAggregate.getAggregatedAttribute(), keepAggregate.getAggregatedAttribute(),
keepAggregate.getSource(), keepAggregate.getTarget()) keepAggregate.getSource(), keepAggregate.getTarget())
) )
.caseOf(KeepReferenceExpression.class, this::writeKeepReference) .caseOf(KeepReferenceExpression.class, keepRef -> {
.caseOf(KeepSubTypeExpression.class, this::writeKeepSubType) String header = String.format("keep %s %s as type %s {" + delimiter, keepRef.getReferenceDirection().name().toLowerCase(), keepRef.getAttribute(), keepRef.getTarget());
.caseOf(KeepSuperTypeExpression.class, this::writeKeepSuperType) return writeCompound(keepRef, header);
})
.caseOf(KeepSubTypeExpression.class, keepSubtype -> {
String header = String.format("keep subtype %s as type %s {" + delimiter, keepSubtype.getType(), keepSubtype.getTarget());
return writeCompound(keepSubtype, header);
})
.caseOf(KeepSuperTypeExpression.class, keepSupertype -> {
String header = String.format("keep supertype %s as type %s {" + delimiter, keepSupertype.getType(), keepSupertype.getTarget());
return writeCompound(keepSupertype, header);
})
.runOrThrow(); .runOrThrow();
} }
private String writeKeepReference(KeepReferenceExpression keepRef) { private String writeCompound(CompoundKeepExpression compoundKeep, String header) {
StringJoiner refJoiner = new StringJoiner(" "); StringJoiner refJoiner = new StringJoiner(" ");
String header = String.format("keep %s %s as type %s {\n", keepRef.getReferenceDirection().name().toLowerCase(), keepRef.getAttribute(), keepRef.getTarget());
refJoiner.add(header); refJoiner.add(header);
keepRef.getKeeps().forEach(keep -> refJoiner.add(writeKeep(keep) + "\n")); increaseIndentationIfNecessary();
refJoiner.add("}"); compoundKeep.getKeeps().forEach(keep -> refJoiner.add(currentIndentation + writeKeep(keep) + delimiter));
decreaseIndentationIfNecessary();
refJoiner.add(currentIndentation + "}");
return refJoiner.toString(); return refJoiner.toString();
} }
private String writeKeepSubType(KeepSubTypeExpression keepSubType) { private void increaseIndentationIfNecessary() {
StringJoiner refJoiner = new StringJoiner(" "); if (useNewlineDelimiters) {
currentIndentation += DEFAULT_INDENTATION;
String header = String.format("keep subtype %s as type %s {\n", keepSubType.getType(), keepSubType.getTarget()); }
refJoiner.add(header);
keepSubType.getKeeps().forEach(keep -> refJoiner.add(writeKeep(keep) + "\n"));
refJoiner.add("}");
return refJoiner.toString();
} }
private String writeKeepSuperType(KeepSuperTypeExpression keepSuperType) { private void decreaseIndentationIfNecessary() {
StringJoiner refJoiner = new StringJoiner(" "); if (useNewlineDelimiters) {
int deletionIdx = currentIndentation.lastIndexOf(DEFAULT_INDENTATION);
String header = String.format("keep supertype %s as type %s {\n", keepSuperType.getType(), keepSuperType.getTarget()); currentIndentation = currentIndentation.substring(0, deletionIdx);
}
refJoiner.add(header);
keepSuperType.getKeeps().forEach(keep -> refJoiner.add(writeKeep(keep) + "\n"));
refJoiner.add("}");
return refJoiner.toString();
} }
} }
package org.rosi_project.model_sync.model_join.representation.writer;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.rosi_project.model_sync.model_join.representation.writer.TestedModels.Listing2;
class FileBasedModelJoinWriterAcceptanceTests {
private static final Logger log = Logger.getLogger(FileBasedModelJoinWriterAcceptanceTests.class.getSimpleName());
private List<File> createdFiles = new ArrayList<>();
@AfterEach
void tearDown() {
createdFiles.forEach(file -> {
boolean result = file.delete();
log.info(String.format("Deleting file %s, successfull=%s", file, result));
});
createdFiles.clear();
}
/**
* Depends on {@link StringBasedModelJoinWriterAcceptanceTests#writeProducesListing2FromTechnicalReport()}
*/
@Test
void writeProducesListing2FromTechnicalReport() {
File outputFile = new File("listing2.modeljoin");
registerCreatedFile(outputFile);
FileBasedModelJoinWriter writer = new FileBasedModelJoinWriter(outputFile);
writer.write(Listing2.asModelJoin);
logFileContent(outputFile);
String expectedContent = StringBasedModelJoinWriter.withNewlines().write(Listing2.asModelJoin);
assertThat(outputFile).exists();
assertThat(outputFile).hasContent(expectedContent);
}
private void registerCreatedFile(File newFile) {
createdFiles.add(newFile);
log.info(String.format("Created new file %s", newFile));
}
private void logFileContent(File file) {
try {
String contents = Files.lines(file.toPath()).collect(Collectors.joining());
log.info(String.format("Content of file %s:", file));
log.info(contents);
} catch (IOException e) {
log.severe(String.format("Could not read file %s", file));
}
}
}
package org.rosi_project.model_sync.model_join.representation.writer; package org.rosi_project.model_sync.model_join.representation.writer;
import static org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder.attributes;
import static org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder.outgoing;
import static org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder.supertype;
import static org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder.thetaJoin;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import java.util.logging.Logger;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.rosi_project.model_sync.model_join.representation.core.AttributePath; import org.rosi_project.model_sync.model_join.representation.writer.TestedModels.Listing2;
import org.rosi_project.model_sync.model_join.representation.core.ClassResource;
import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression;
import org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder;
import org.rosi_project.model_sync.util.NeedsCleanup;
@RunWith(JUnitPlatform.class)
@NeedsCleanup
class StringBasedModelJoinWriterAcceptanceTests { class StringBasedModelJoinWriterAcceptanceTests {
/* private static final Logger log = Logger.getLogger(StringBasedModelJoinWriterAcceptanceTests.class.getSimpleName());
* The technical report which forms the basis of the tests here is
*
* Burger, E. et Al.: "ModelJoin - A Textual Domain-Specific Language for the Combination of
* Heterogeneous Models"; Karslruhe Institute of Technololgy 2014
*/
/**
* @see TestedModels
*/
@Test @Test
void writeProducesListing2FromTechnicalReport() { void writeProducesListing2FromTechnicalReport() {
StringBasedModelJoinWriter writer = StringBasedModelJoinWriter.withNewlines();
String writerOutput = writer.write(Listing2.asModelJoin);
ClassResource imdbFilm = ClassResource.from("imdb", "Film"); log.info("Writer output:");
ClassResource libraryVideoCassette = ClassResource.from("library", "VideoCassette"); log.info(writerOutput);
ClassResource libraryAudioVisualItem = ClassResource.from("library", "AudioVisualItem");
ClassResource jointargetMovie = ClassResource.from("jointarget", "Movie");
ClassResource jointargetVote = ClassResource.from("jointarget", "Vote");
ClassResource jointargetMediaItem = ClassResource.from("jointarget", "MediaItem");
AttributePath imdbFilmYear = AttributePath.from(imdbFilm, "year");
AttributePath imdbFilmVotes = AttributePath.from(imdbFilm, "votes");
AttributePath imdbVoteScore = AttributePath.from("imdb.Vote", "score");
AttributePath libraryAudioVisualItemMinutesLength = AttributePath.from(libraryAudioVisualItem, "minutesLength");
ModelJoinExpression listing2 = ModelJoinBuilder.createNewModelJoin()
.add(thetaJoin()
.join(imdbFilm)
.with(libraryVideoCassette)
.as(jointargetMovie)
.where(
"library.VideoCassette.cast->forAll (p | imdb.Film.figures->playedBy->exists (a | p.firstname.concat(\" \") .concat(p.lastName) == a.name))")
.keep(attributes(imdbFilmYear))
.keep(outgoing(imdbFilmVotes)
.as(jointargetVote)
.keep(attributes(imdbVoteScore))
.buildExpression()
)
.keep(supertype(libraryAudioVisualItem)
.as(jointargetMediaItem)
.keep(attributes(libraryAudioVisualItemMinutesLength))
.buildExpression()
)
.done())
.build();
StringBasedModelJoinWriter writer = new StringBasedModelJoinWriter();
String writerOutput = writer.write(listing2);
String sanitizedWriterOutput = sanitize(writerOutput); String sanitizedWriterOutput = sanitize(writerOutput);
String expectedOutput = sanitize("theta join imdb.Film with library.VideoCassette as jointarget.Movie\n" String expectedOutput = sanitize(Listing2.asString);
+ "where library.VideoCassette.cast->forAll (p | imdb.Film.figures->playedBy->exists (a | p.\n"
+ "firstname.concat(\" \") .concat(p.lastName) == a.name)) {\n"
+ "keep attributes imdb.Film.year\n"
+ "keep outgoing imdb.Film.votes as type jointarget.Vote {\n"
+ "keep attributes imdb.Vote.score\n"
+ "}\n"
+ "keep supertype library.AudioVisualItem as type jointarget.MediaItem {\n"
+ " keep attributes library.AudioVisualItem.minutesLength\n"
+ "}\n"
+ "}");
// Although AssertJ provides an isEqualToIgnoringWhitespace as well as an // Although AssertJ provides an isEqualToIgnoringWhitespace as well as an
// isEqualToIgnoringNewline method, there's none that ignores both. Thus we reside to manual // isEqualToIgnoringNewline method, there's none that ignores both. Thus we reside to manual
......
package org.rosi_project.model_sync.model_join.representation.writer;
import static org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder.attributes;
import static org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder.outgoing;
import static org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder.supertype;
import static org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder.thetaJoin;
import org.rosi_project.model_sync.model_join.representation.core.AttributePath;
import org.rosi_project.model_sync.model_join.representation.core.ClassResource;
import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression;
import org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder;
class TestedModels {
/*
* The technical report which forms the basis of the tests here is
*
* Burger, E. et Al.: "ModelJoin - A Textual Domain-Specific Language for the Combination of
* Heterogeneous Models"; Karslruhe Institute of Technololgy 2014
*/
static class Listing2 {
static final ClassResource imdbFilm = ClassResource.from("imdb", "Film");
static final ClassResource libraryVideoCassette = ClassResource.from("library", "VideoCassette");
static final ClassResource libraryAudioVisualItem = ClassResource.from("library", "AudioVisualItem");
static final ClassResource jointargetMovie = ClassResource.from("jointarget", "Movie");
static final ClassResource jointargetVote = ClassResource.from("jointarget", "Vote");
static final ClassResource jointargetMediaItem = ClassResource.from("jointarget", "MediaItem");
static final AttributePath imdbFilmYear = AttributePath.from(imdbFilm, "year");
static final AttributePath imdbFilmVotes = AttributePath.from(imdbFilm, "votes");
static final AttributePath imdbVoteScore = AttributePath.from("imdb.Vote", "score");
static final AttributePath libraryAudioVisualItemMinutesLength = AttributePath.from(libraryAudioVisualItem, "minutesLength");
static final ModelJoinExpression asModelJoin = ModelJoinBuilder.createNewModelJoin()
.add(thetaJoin()
.join(imdbFilm)
.with(libraryVideoCassette)
.as(jointargetMovie)
.where(
"library.VideoCassette.cast->forAll (p | imdb.Film.figures->playedBy->exists (a | p.firstname.concat(\" \") .concat(p.lastName) == a.name))")
.keep(attributes(imdbFilmYear))
.keep(outgoing(imdbFilmVotes)
.as(jointargetVote)
.keep(attributes(imdbVoteScore))
.buildExpression()
)
.keep(supertype(libraryAudioVisualItem)
.as(jointargetMediaItem)
.keep(attributes(libraryAudioVisualItemMinutesLength))
.buildExpression()
)
.done())
.build();
static final String asString = "theta join imdb.Film with library.VideoCassette as jointarget.Movie\n"
+ "where library.VideoCassette.cast->forAll (p | imdb.Film.figures->playedBy->exists (a | p.\n"
+ "firstname.concat(\" \") .concat(p.lastName) == a.name)) {\n"
+ "keep attributes imdb.Film.year\n"
+ "keep outgoing imdb.Film.votes as type jointarget.Vote {\n"
+ "keep attributes imdb.Vote.score\n"
+ "}\n"
+ "keep supertype library.AudioVisualItem as type jointarget.MediaItem {\n"
+ " keep attributes library.AudioVisualItem.minutesLength\n"
+ "}\n"
+ "}";
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment