diff --git a/.gitignore b/.gitignore index dfd7c3e83132deec18e16cf9be84cb2171cf75a7..5e142013c2bdbc64d4fe3558748328a282bbfc7a 100644 --- a/.gitignore +++ b/.gitignore @@ -177,3 +177,7 @@ local.properties output/ doc/ [lL]ocal[tT]est* + +# ANTLR + +gen/ diff --git a/build.sbt b/build.sbt index 1d20ffe58cde25af7a5622357d72de6d3daf9b7b..408f0db69bdce446a1776a7ddb5393f33fc34239 100644 --- a/build.sbt +++ b/build.sbt @@ -1,3 +1,4 @@ +import com.simplytyped.Antlr4Plugin.autoImport.antlr4PackageName import sbt.Keys.{libraryDependencies, scalacOptions, version} val emfcommonVersion = "2.12.0" @@ -40,5 +41,12 @@ lazy val generator = (project in file(".")) case x => val oldStrategy = (assemblyMergeStrategy in assembly).value oldStrategy(x) - } - ).dependsOn(syncProvider, rsumProvider) + }, + ).dependsOn( + syncProvider, + rsumProvider + ).enablePlugins(Antlr4Plugin) + +antlr4PackageName in Antlr4 := Some("org.rosi_project.model_sync.model_join.representation.parser.antlr.generated") +antlr4GenVisitor in Antlr4 := true +antlr4GenListener in Antlr4 := false diff --git a/lib/ModelSyncProvider b/lib/ModelSyncProvider index b198d4633ab67b708a81e399be95596a5b4a0b6c..619bbf1efcfee406a134412275928322d9d28e05 160000 --- a/lib/ModelSyncProvider +++ b/lib/ModelSyncProvider @@ -1 +1 @@ -Subproject commit b198d4633ab67b708a81e399be95596a5b4a0b6c +Subproject commit 619bbf1efcfee406a134412275928322d9d28e05 diff --git a/project/build.properties b/project/build.properties index 8db5ca22266ea113c2b39c92dddb73c83e61ffee..cabf73b45107a5feb964e2c564c9687faafffdbe 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.2.1 \ No newline at end of file +sbt.version = 1.2.7 diff --git a/project/sbt-antlr4.sbt b/project/sbt-antlr4.sbt new file mode 100644 index 0000000000000000000000000000000000000000..7b26d1fc7b9ee12df151e57cb9e2528c1a4f8d9f --- /dev/null +++ b/project/sbt-antlr4.sbt @@ -0,0 +1,2 @@ + +addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.2") diff --git a/src/main/antlr4/ModelJoin.g4 b/src/main/antlr4/ModelJoin.g4 new file mode 100644 index 0000000000000000000000000000000000000000..e5ceb0db71cdbe18fb99fc044b2b95dac13b20f7 --- /dev/null +++ b/src/main/antlr4/ModelJoin.g4 @@ -0,0 +1,105 @@ +/* + * ModelJoin.g4 + * + * Defines the structure of our ModelJoin grammar + * + * Author: Rico Bergmann + */ +grammar ModelJoin; + +/* + * Parser + */ +modeljoin : join+ EOF ; +join : (naturaljoin | thetajoin | outerjoin) AS classres + ( OPENCURLYBRAKET + keepattributesexpr* + keepaggregatesexpr* + keepexpr* + CLOSEDCURLYBRAKET )? ; +naturaljoin : NATURAL JOIN classres WITH classres ; +thetajoin : THETA JOIN classres WITH classres WHERE oclcond; +outerjoin : (leftouterjoin | rightouterjoin) OUTER JOIN classres WITH classres; +leftouterjoin : LEFT ; +rightouterjoin : RIGHT ; + +keepattributesexpr : KEEP ATTRIBUTES attrres (COMMA attrres)* ; +keepaggregatesexpr : KEEP AGGREGATE aggrtype + OPENBRAKET relattr CLOSEDBRAKET + OVER classres AS attrres; +keepexpr : (keeptypeexpr | keepoutgoingexpr | keepincomingexpr) + ( OPENCURLYBRAKET + keepattributesexpr* + keepaggregatesexpr* + keepexpr* + CLOSEDCURLYBRAKET)? ; +keeptypeexpr : keepsupertypeexpr | keepsubtypeexpr ; +keepsupertypeexpr : KEEP SUPERTYPE classres ( AS TYPE classres )? ; +keepsubtypeexpr : KEEP SUBTYPE classres ( AS TYPE classres )? ; +keepoutgoingexpr : KEEP OUTGOING attrres ( AS TYPE classres )? ; +keepincomingexpr : KEEP INCOMING attrres (AS TYPE classres )? ; + +classres : WORD (DOT WORD)* ; +attrres : WORD (DOT WORD)+ ; +relattr : WORD ; +oclcond : (WORD | NUMBER | specialchar | WHITESPACE | NEWLINE)+; +aggrtype : SUM | AVG | MIN | MAX | SIZE ; + +specialchar : DOT + | OPENBRAKET + | CLOSEDBRAKET + | OPENCURLYBRAKET + | CLOSEDCURLYBRAKET + | COMMA + | UNDERSCORE + | SPECIALCHAR ; + +/* + * Lexer + */ + +fragment LOWERCASE : [a-z] ; +fragment UPPERCASE : [A-Z] ; +fragment ANYCASE : LOWERCASE | UPPERCASE ; +fragment DIGIT : [0-9] ; + +OPENBRAKET : '(' ; +CLOSEDBRAKET : ')' ; +OPENCURLYBRAKET : '{' ; +CLOSEDCURLYBRAKET : '}' ; +DOT : '.' ; +COMMA : ',' ; +UNDERSCORE : '_' ; +SPECIALCHAR : [-><!="'] ; + +JOIN : 'join' ; +NATURAL : 'natural' ; +THETA : 'theta' ; +WHERE : 'where' ; +OUTER : 'outer' ; +RIGHT : 'right' ; +LEFT : 'left' ; +WITH : 'with' ; +AS : 'as' ; + +KEEP : 'keep' ; +ATTRIBUTES : 'attributes' ; +AGGREGATE : 'aggregate' ; +SUPERTYPE : 'supertype' ; +SUBTYPE : 'subtype' ; +OUTGOING : 'outgoing' ; +INCOMING : 'incoming' ; +TYPE : 'type' ; +OVER : 'over' ; + +SUM : 'sum' ; +AVG : 'avg' ; +MIN : 'min' ; +MAX : 'max' ; +SIZE : 'size' ; + +WORD : ANYCASE (ANYCASE | DIGIT | UNDERSCORE)* ; +NUMBER : [+-]? DIGIT+ DOT? DIGIT* ; + +WHITESPACE : ' ' -> skip ; +NEWLINE : ('\r'? '\n' | '\r')+ -> skip ; diff --git a/src/main/doc b/src/main/doc index 89e2af4e060ae7c39c261fde55ba9140caf85853..83a9646a8d0f69d76443d38208c9df140790a0f7 160000 --- a/src/main/doc +++ b/src/main/doc @@ -1 +1 @@ -Subproject commit 89e2af4e060ae7c39c261fde55ba9140caf85853 +Subproject commit 83a9646a8d0f69d76443d38208c9df140790a0f7 diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/core/OCLConstraint.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/core/OCLConstraint.java index 91253129c1f74bd7753e63008dd42d5ad38fd1ef..6f8afef2b4ba2623225ac2f1aed8ed72a6d838f5 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/core/OCLConstraint.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/core/OCLConstraint.java @@ -1,6 +1,5 @@ package org.rosi_project.model_sync.model_join.representation.core; -import java.util.Objects; import javax.annotation.Nonnull; /** diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/JoinExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/JoinExpression.java index 97f4169e44b24b72f20cd68ff3b9b08d1b4186a6..956a42704e52a9fdc0d34b091075d3f61a88db7a 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/JoinExpression.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/JoinExpression.java @@ -22,6 +22,8 @@ import org.rosi_project.model_sync.model_join.representation.core.ClassResource; * * @author Rico Bergmann * @see NaturalJoinExpression + * @see OuterJoinExpression + * @see ThetaJoinExpression */ public abstract class JoinExpression implements Iterable<KeepExpression> { @@ -32,6 +34,8 @@ public abstract class JoinExpression implements Iterable<KeepExpression> { /** * The {@code natural join} combines two classes based on attributes with equal name and type. + * + * @see NaturalJoinExpression */ NATURAL, @@ -39,12 +43,16 @@ public abstract class JoinExpression implements Iterable<KeepExpression> { * The {@code outer join} works like the {@link #NATURAL} one, but leaves instances from one * class with no corresponding instance in the other class according to the {@code outer join * type}. See the subclass for details. + * + * @see OuterJoinExpression */ OUTER, /** * The {@code theta join} is more general than the {@link #NATURAL} and {@link #OUTER} one as * it enables an arbitrary criteria to define whether two instances are "joinable" or not. + * + * @see ThetaJoinExpression */ THETA } diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAggregateExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAggregateExpression.java index ccef41ebee66c2853f84328c88d3b35e56ab62ea..d0da5aa0b03a4d87b7cacf86d1d7c8442e577eb1 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAggregateExpression.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAggregateExpression.java @@ -225,6 +225,11 @@ public class KeepAggregateExpression extends KeepExpression { return target; } + @Override + public void accept(@Nonnull KeepExpressionVisitor visitor) { + visitor.visit(this); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAttributesExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAttributesExpression.java index 507822727f4401a0416f15b1b6b80df327ad8630..99cdb8109e16e584442425b0eac0aceefbe49337 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAttributesExpression.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAttributesExpression.java @@ -49,6 +49,11 @@ public class KeepAttributesExpression extends KeepExpression { return new ArrayList<>(attributes); } + @Override + public void accept(@Nonnull KeepExpressionVisitor visitor) { + visitor.visit(this); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepCalculatedExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepCalculatedExpression.java index e028f412e492434e7e7d32667c20af4e480be394..8dbafbae1a62504772bb0968772a2f05457222cb 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepCalculatedExpression.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepCalculatedExpression.java @@ -94,6 +94,11 @@ public class KeepCalculatedExpression extends KeepExpression { return target; } + @Override + public void accept(@Nonnull KeepExpressionVisitor visitor) { + visitor.visit(this); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepExpression.java index 8fc8846d5b7fef54ca5f67b2b091d7bf3a00d251..bd879001f642b3dc1a5beef04e196f86534e97ab 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepExpression.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepExpression.java @@ -1,5 +1,7 @@ package org.rosi_project.model_sync.model_join.representation.grammar; +import javax.annotation.Nonnull; + /** * A {@code KeepExpression} configures the resulting model of a join. * <p> @@ -10,6 +12,49 @@ package org.rosi_project.model_sync.model_join.representation.grammar; */ public abstract class KeepExpression { + /** + * The {@code visitor} enables the execution of arbitrary algorithms on {@code KeepExpression}s + * without taking care of the actual traversal of the expression tree. + */ + public interface KeepExpressionVisitor { + + /** + * Runs the algorithm as appropriate for {@link KeepAggregateExpression}s. + */ + void visit(@Nonnull KeepAggregateExpression expression); + + /** + * Runs the algorithm as appropriate for {@link KeepAttributesExpression}s. + */ + void visit(@Nonnull KeepAttributesExpression expression); + + /** + * Runs the algorithm as appropriate for {@link KeepCalculatedExpression}s. + */ + void visit(@Nonnull KeepCalculatedExpression expression); + + /** + * Runs the algorithm as appropriate for {@link KeepReferenceExpression}s. + */ + void visit(@Nonnull KeepReferenceExpression expression); + + /** + * Runs the algorithm as appropriate for {@link KeepSubTypeExpression}s. + */ + void visit(@Nonnull KeepSubTypeExpression expression); + + /** + * Runs the algorithm as appropriate for {@link KeepSuperTypeExpression}s. + */ + void visit(@Nonnull KeepSuperTypeExpression expression); + + } + // TODO `as` statements may be omitted !?!? ... (╯°□°)╯︵ ┻━┻ + /** + * Applies a {@code visitor} to {@code this} expression structure. + */ + public abstract void accept(@Nonnull KeepExpressionVisitor visitor); + } diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepReferenceExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepReferenceExpression.java index 2a33e1a4f3dc061d9b1e4143e1fa1328466c49a4..b111ca3e3bead3cc6299424bd930c31a74dc841b 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepReferenceExpression.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepReferenceExpression.java @@ -5,25 +5,126 @@ import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; 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.util.Assert; // TODO the Xtext grammar also specifies an optional `as reference` part - what does that part do? +// most likely `as reference` will refer to another type that was already defined in its dedicated +// join statement. /** - * A {@code KeepReferenceExpression} retains links to other instances in the - * original models. All objects that are referenced in the base models will also - * be available in the resulting ModelJoin views. + * A {@code KeepReferenceExpression} retains links to other instances in the original models. All + * objects that are referenced in the base models will also be available in the resulting ModelJoin + * views. * <p> - * TODO complete doc + * As references are directional, each expression also distinguishes between {@code outgoing} and + * {@code incoming} references - depending on whether the model class of {@code this} expression + * owns the reference ({@code outgoing}) or instances of this model class are being referenced + * ({@code incoming}). + * <p> + * The referenced instances will be made available according to a number of keep statements which + * specify the attributes to include. This boils down to the creation of a nested ModelJoin view for + * these instances. * * @author Rico Bergmann */ public class KeepReferenceExpression extends KeepExpression { + /** + * The {@code ReferenceDirection} defines whether a reference is owned by the containing model + * class or whether it is the class being referenced. + */ public enum ReferenceDirection { + + /** + * Indicates that the containing model class owns the reference. + */ OUTGOING, + + /** + * Indicates that the containing model class is referenced by some other class. + */ INCOMING } + /** + * The {@code KeepReferenceBuilder} enables the construction of new {@code + * KeepReferenceExpression} instances through a nice and fluent interface. + * + * @author Rico Bergmann + */ + public static class KeepReferenceBuilder { + + private ReferenceDirection referenceDirection; + private AttributePath attribute; + private ClassResource target; + + @Nonnull + private List<KeepExpression> keeps; + + /** + * Default constructor. + */ + private KeepReferenceBuilder() { + this.keeps = new ArrayList<>(); + } + + /** + * Creates an outgoing reference for the given attribute. + */ + @Nonnull + public KeepReferenceBuilder outgoing(@Nonnull AttributePath attribute) { + this.referenceDirection = ReferenceDirection.OUTGOING; + this.attribute = attribute; + return this; + } + + /** + * Creates an incoming reference for the given attribute. + */ + @Nonnull + public KeepReferenceBuilder incoming(@Nonnull AttributePath attribute) { + this.referenceDirection = ReferenceDirection.INCOMING; + this.attribute = attribute; + return this; + } + + /** + * Specifies the name of the attribute in the target join. + */ + @Nonnull + public KeepReferenceBuilder as(@Nonnull ClassResource target) { + this.target = target; + return this; + } + + /** + * Adds an attribute to the view for the referenced instances. + */ + @Nonnull + public KeepReferenceBuilder keep(@Nonnull KeepExpression keepExpression) { + this.keeps.add(keepExpression); + return this; + } + + /** + * Finishes the construction process. + */ + @Nonnull + public KeepReferenceExpression buildExpression() { + Assert.noNullArguments("All components must be specified", attribute, referenceDirection, target, keeps); + return new KeepReferenceExpression(attribute, referenceDirection, target, keeps); + } + } + + /** + * Starts the creation process for a new {@code KeepReferenceExpression}. + */ + @Nonnull + public static KeepReferenceBuilder keep() { + return new KeepReferenceBuilder(); + } + @Nonnull private final AttributePath attribute; @@ -31,15 +132,24 @@ public class KeepReferenceExpression extends KeepExpression { private final ReferenceDirection referenceDirection; @Nonnull - private final AttributePath target; + private final ClassResource target; @Nonnull private final List<KeepExpression> keeps; + /** + * Full constructor. + * + * @param attribute the attribute of the source model which contains the references + * @param referenceDirection whether the source model owns the reference or is being + * referenced + * @param target the name of the class which enca + * @param keeps the statements which should be build the "nested" view for the references + */ public KeepReferenceExpression( @Nonnull AttributePath attribute, @Nonnull ReferenceDirection referenceDirection, - @Nonnull AttributePath target, + @Nonnull ClassResource target, @Nonnull List<KeepExpression> keeps) { this.attribute = attribute; this.referenceDirection = referenceDirection; @@ -47,26 +157,46 @@ public class KeepReferenceExpression extends KeepExpression { this.keeps = keeps; } + /** + * Provides the attribute of the source model which contains the references. + */ @Nonnull public AttributePath getAttribute() { return attribute; } + /** + * Provides the kind of reference, that is whether the containing Join owns the reference or is + * being referenced by another model class or Join. + */ @Nonnull public ReferenceDirection getReferenceDirection() { return referenceDirection; } + /** + * Provides the name of the attribute under which the referenced instances should be made + * available. + */ @Nonnull - public AttributePath getTarget() { + public ClassResource getTarget() { return target; } + /** + * Provides all {@code KeepExpression keep expressions} that should be used to build the Join for + * the referenced instances. + */ @Nonnull public List<KeepExpression> getKeeps() { return new ArrayList<>(keeps); } + @Override + public void accept(@Nonnull KeepExpressionVisitor visitor) { + visitor.visit(this); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -76,8 +206,7 @@ public class KeepReferenceExpression extends KeepExpression { return false; } KeepReferenceExpression that = (KeepReferenceExpression) o; - return attribute.equals(that.attribute) && - target.equals(that.target); + return attribute.equals(that.attribute) && target.equals(that.target); } @Override diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSubTypeExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSubTypeExpression.java index 299ffa50377f322e0cfa8eb6e270f9ea961a052d..fd732649f7fdb5337a49007a18edea44d04f3947 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSubTypeExpression.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSubTypeExpression.java @@ -1,47 +1,144 @@ package org.rosi_project.model_sync.model_join.representation.grammar; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; -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.util.Assert; /** + * A {@code KeepSubTypeExpression} instructs the {@code ModelJoin} runtime to instantiate the most + * specific type that inherits from this type as well. The additional instance will be built + * according to the {@code KeepExpression keep statements} that it is constructed of. + * <p> + * In order to prevent ambiguities a {@code KeepSubTypeExpression} may only be specified on joins + * that do not participate in another {@link JoinExpression join statement}. + * * @author Rico Bergmann */ public class KeepSubTypeExpression extends KeepExpression { + /** + * The {@code KeepSubTypeBuilder} enables the construction of new {@code KeepSubTypeExpression} + * instances through a nice and fluent interface. + * + * @author Rico Bergmann + */ + public static class KeepSubTypeBuilder { + + private ClassResource typeToKeep; + private ClassResource target; + private List<KeepExpression> keeps; + + /** + * Default constructor. + */ + private KeepSubTypeBuilder() { + this.keeps = new ArrayList<>(); + } + + /** + * Specifies the subtype that should be instantiated. + */ + @Nonnull + public KeepSubTypeBuilder subtype(@Nonnull ClassResource type) { + this.typeToKeep = type; + return this; + } + + /** + * Specifies the name of the class that should be generated for the subtype instances. + */ + @Nonnull + public KeepSubTypeBuilder as(@Nonnull ClassResource target) { + this.target = target; + return this; + } + + /** + * Adds a {@link KeepExpression keep statement} for the subtype-class. The described attribute + * will be initialized for each instantiated subtype element. + */ + @Nonnull + public KeepSubTypeBuilder keep(@Nonnull KeepExpression keep) { + this.keeps.add(keep); + return this; + } + + /** + * Finishes the construction process. + */ + @Nonnull + public KeepSubTypeExpression buildExpression() { + Assert.noNullArguments("All components must be specified", typeToKeep, target, keeps); + return new KeepSubTypeExpression(typeToKeep, target, keeps); + } + + } + + /** + * Starts the creation process for a new {@code KeepSubTypeExpression}. + */ + @Nonnull + public static KeepSubTypeBuilder keep() { + return new KeepSubTypeBuilder(); + } + @Nonnull private final ClassResource typeToKeep; @Nonnull - private final AttributePath target; + private final ClassResource target; @Nonnull private final List<KeepExpression> keeps; + /** + * Full constructor. + * + * @param typeToKeep the subtype that should be instantiated. + * @param target the name of the view that should be generated for all matching instances + * @param keeps the keep statements that should form the attributes of the generated view + * instances + */ public KeepSubTypeExpression( @Nonnull ClassResource typeToKeep, - @Nonnull AttributePath target, + @Nonnull ClassResource target, @Nonnull List<KeepExpression> keeps) { this.typeToKeep = typeToKeep; this.target = target; this.keeps = keeps; } + /** + * Provides the subtype that should be instantiated. + */ @Nonnull public ClassResource getType() { return typeToKeep; } + /** + * Provides the name of the view that should be generated for all matching instances. + */ @Nonnull - public AttributePath getTarget() { + public ClassResource getTarget() { return target; } + /** + * Provides all {@code KeepExpression keep expressions} that should be used to build the Join for + * the instances of the subclass instances. + */ @Nonnull public List<KeepExpression> getKeeps() { - return keeps; + return new ArrayList<>(keeps); + } + + @Override + public void accept(@Nonnull KeepExpressionVisitor visitor) { + visitor.visit(this); } @Override diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSuperTypeExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSuperTypeExpression.java index c5801712ed50d2f42dae7f923904ec88e2859e54..703adefd8e24e401f39d7d68c20fa8d1449150ab 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSuperTypeExpression.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSuperTypeExpression.java @@ -1,48 +1,143 @@ package org.rosi_project.model_sync.model_join.representation.grammar; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; -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.util.Assert; + +// TODO add doc /** + * A {@code KeepSuperTypeExpression} instructs the {@code ModelJoin} runtime to instantiate the a + * supertype of some class in a join along an instance of that class. The additional instance will + * be built according to the {@code KeepExpression keep statements} that it is constructed of. * * @author Rico Bergmann */ public class KeepSuperTypeExpression extends KeepExpression { + /** + * The {@code KeepSuperTypeBuilder} enables the construction of new {@code KeepSuperTypeExpression} + * instances through a nice and fluent interface. + * + * @author Rico Bergmann + */ + public static class KeepSuperTypeBuilder { + + private ClassResource typeToKeep; + private ClassResource target; + private List<KeepExpression> keeps; + + /** + * Default constructor. + */ + private KeepSuperTypeBuilder() { + this.keeps = new ArrayList<>(); + } + + /** + * Specifies the supertype that should be instantiated. + */ + @Nonnull + public KeepSuperTypeBuilder supertype(@Nonnull ClassResource type) { + this.typeToKeep = type; + return this; + } + + /** + * Specifies the name of the class that should be generated for the subtype instances. + */ + @Nonnull + public KeepSuperTypeBuilder as(@Nonnull ClassResource target) { + this.target = target; + return this; + } + + /** + * Adds a {@link KeepExpression keep statement} for the subtype-class. The described attribute + * will be initialized for each instantiated subtype element. + */ + @Nonnull + public KeepSuperTypeBuilder keep(@Nonnull KeepExpression keep) { + this.keeps.add(keep); + return this; + } + + /** + * Finishes the construction process. + */ + @Nonnull + public KeepSuperTypeExpression buildExpression() { + Assert.noNullArguments("All components must be specified", typeToKeep, target, keeps); + return new KeepSuperTypeExpression(typeToKeep, target, keeps); + } + + } + + /** + * Starts the creation process for a new {@code KeepSuperTypeExpression}. + */ + @Nonnull + public static KeepSuperTypeBuilder keep() { + return new KeepSuperTypeBuilder(); + } + @Nonnull private final ClassResource typeToKeep; @Nonnull - private final AttributePath target; + private final ClassResource target; @Nonnull private final List<KeepExpression> keeps; + /** + * Full constructor. + * + * @param typeToKeep the supertype that should be instantiated. + * @param target the name of the view that should be generated for all matching instances + * @param keeps the keep statements that should form the attributes of the generated view + * instances + */ public KeepSuperTypeExpression( @Nonnull ClassResource typeToKeep, - @Nonnull AttributePath target, + @Nonnull ClassResource target, @Nonnull List<KeepExpression> keeps) { this.typeToKeep = typeToKeep; this.target = target; this.keeps = keeps; } + /** + * Provides the supertype that should be instantiated. + */ @Nonnull public ClassResource getType() { return typeToKeep; } + /** + * Provides the name of the view that should be generated for all matching instances. + */ @Nonnull - public AttributePath getTarget() { + public ClassResource getTarget() { return target; } + /** + * Provides all {@code KeepExpression keep expressions} that should be used to build the Join for + * the instances of the superclass instances. + */ @Nonnull public List<KeepExpression> getKeeps() { - return keeps; + return new ArrayList<>(keeps); + } + + @Override + public void accept(@Nonnull KeepExpressionVisitor visitor) { + visitor.visit(this); } @Override diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParser.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParser.java index 93d6bf8036102f80382c7a2dbcebe2f9d6b3e701..87c6f0cc6383feb4e067c767e08bd12c303d308e 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParser.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParser.java @@ -1,33 +1,24 @@ package org.rosi_project.model_sync.model_join.representation.parser; -import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; import java.util.Optional; -import java.util.regex.Pattern; import javax.annotation.Nonnull; import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression; -import org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder; /** - * The {@code ModelJoinParser} reads a {@code ModelJoin} file from disk and creates matching - * representation for it. + * The {@code ModelJoinParser} reads a {@code ModelJoin} file from disk and creates a matching + * logical representation for it. * * @author Rico Bergmann */ -public class ModelJoinParser { +public interface ModelJoinParser { /** * Converts the {@code ModelJoin} file into a {@link ModelJoinExpression}, wrapped into an {@code * Optional}. If parsing fails, an empty {@code Optional} will be returned. */ @Nonnull - public static Optional<ModelJoinExpression> read(@Nonnull File modelFile) { - ModelJoinParser parser = new ModelJoinParser(modelFile, ErrorReportingStrategy.OPTIONAL); - return parser.run(); - } + Optional<ModelJoinExpression> read(@Nonnull File modelFile); /** * Converts the {@code ModelJoin} file into a {@link ModelJoinExpression}. If parsing fails, a @@ -36,149 +27,6 @@ public class ModelJoinParser { * @throws ModelJoinParsingException if the model may not be parsed for some reason. */ @Nonnull - public static ModelJoinExpression readOrThrow(@Nonnull File modelFile) { - ModelJoinParser parser = new ModelJoinParser(modelFile, ErrorReportingStrategy.EXCEPTION); - Optional<ModelJoinExpression> result = parser.run(); + ModelJoinExpression readOrThrow(@Nonnull File modelFile); - if (result.isPresent()) { - return result.get(); - } else { - /* - * Theoretically, the parser should throw the exception by itself, so this code should never - * actually run. It is merely here for safety purposes (unchecked access of Optional.get()) - */ - throw new ModelJoinParsingException("Result not present but no exception was thrown either"); - } - } - - /** - * Indicates what should happen if parsing fails. - */ - enum ErrorReportingStrategy { - - /** - * On failure an empty {@code Optional} should be returned. - */ - OPTIONAL, - - /** - * On failure an {@link ModelJoinParsingException} should be thrown. - */ - EXCEPTION - } - - private static final Pattern EMPTY_LINE = Pattern.compile("^\\s*$"); - private static final Pattern IMPORT_STATEMENT = Pattern.compile("^import .*"); - private static final Pattern TARGET_STATEMENT = Pattern.compile("^target .*"); - private static final Pattern JOIN_STATEMENT = Pattern - .compile("^(((left|right) outer)|theta|natural) join .*"); - - @Nonnull - private final File modelFile; - - @Nonnull - private final ErrorReportingStrategy errorReportingStrategy; - - /** - * Full constructor. - * - * @param modelFile the file to parse - * @param errorReportingStrategy what to do in case an error occurs. - */ - private ModelJoinParser(@Nonnull File modelFile, - @Nonnull ErrorReportingStrategy errorReportingStrategy) { - this.modelFile = modelFile; - this.errorReportingStrategy = errorReportingStrategy; - } - - /** - * Creates the {@code ModelJoin} representation from the {@code modelFile}. - * <p> - * Depending on the selected {@code errorReportingStrategy} either an Exception will be thrown, or - * an empty {@code Optional} will be returned if something goes wrong. - */ - private Optional<ModelJoinExpression> run() { - ModelJoinExpression resultingModel; - try (FileInputStream modelInputStream = new FileInputStream(modelFile); - InputStreamReader modelInputReader = new InputStreamReader(modelInputStream); - BufferedReader bufferedModelReader = new BufferedReader(modelInputReader)) { - - ModelJoinBuilder modelJoinBuilder = ModelJoinBuilder.createNewModelJoin(); - readModelFileAndPopulateBuilder(bufferedModelReader, modelJoinBuilder); - resultingModel = modelJoinBuilder.build(); - - } catch (ModelJoinParsingException e) { - switch (errorReportingStrategy) { - case OPTIONAL: - return Optional.empty(); - case EXCEPTION: - throw e; - default: - throw new AssertionError(errorReportingStrategy); - } - } catch (IOException e) { - switch (errorReportingStrategy) { - case OPTIONAL: - return Optional.empty(); - case EXCEPTION: - throw new ModelJoinParsingException(e); - default: - throw new AssertionError(errorReportingStrategy); - } - } - return Optional.of(resultingModel); - } - - /** - * Reads the {@code modelFile} and constructs the corresponding {@code ModelJoin} on the fly. - * <p> - * On error, an {@code IOException} or an {@link ModelJoinParsingException} will be thrown. - * - * @throws ModelJoinParsingException if the model file's content are malformed or another - * component failed - * @throws IOException if the model file might not be read any further - */ - private void readModelFileAndPopulateBuilder(BufferedReader modelReader, - ModelJoinBuilder modelBuilder) throws IOException { - String currentLine; - while ((currentLine = modelReader.readLine()) != null) { - if (lineShouldBeSkipped(currentLine)) { - // the line should be skipped. So we do nothing. - continue; - } else if (lineStartsJoinDeclaration(currentLine)) { - // a new join is being declared. Delegate to the join parser to handle the rest. - JoinParser joinParser = new JoinParser(currentLine, modelReader); - modelBuilder.add(joinParser.run()); - } else { - // we do not know what to do with the current line. Should abort. - throw new ModelJoinParsingException("Unexpected line: '" + currentLine + "'"); - } - } - } - - /** - * Checks, whether the parser should consider the given {@code line} any further, or just ignore - * it. - * <p> - * A line should be skipped, iff. - * <ul> - * <li>It only consists of whitespace characters</li> - * <li>It is an {@code import statement} (as those are not yet considered in the current {@code - * ModelJoin} abstraction)</li> - * <li>It is a {@code target statement} (as those are not yet considered in the current {@code - * ModelJoin} abstraction)</li> - * </ul> - */ - private boolean lineShouldBeSkipped(String line) { - return EMPTY_LINE.matcher(line).matches() // - || IMPORT_STATEMENT.matcher(line).matches() // - || TARGET_STATEMENT.matcher(line).matches(); - } - - /** - * Checks, whether the given {@code line} is the beginning of a {@code join statement}. - */ - private boolean lineStartsJoinDeclaration(String line) { - return JOIN_STATEMENT.matcher(line).matches(); - } } diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParsingException.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParsingException.java index 4a1067c893af62cb25d152315abc32e71a685794..6b72c01ee5d4926b87db1cfa80bc9839e385d104 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParsingException.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParsingException.java @@ -1,8 +1,8 @@ package org.rosi_project.model_sync.model_join.representation.parser; /** - * The {@code ModelJoinParsingException} indicates that a file could not be read into a - * {@code ModelJoin} instance correctly. + * The {@code ModelJoinParsingException} indicates that a {@code ModelJoin} file could not be read + * correctly, e.g. because of missing values or a malformed structure. * * @author Rico Bergmann */ @@ -11,21 +11,21 @@ public class ModelJoinParsingException extends RuntimeException { /** * @see RuntimeException#RuntimeException(String) */ - ModelJoinParsingException(String message) { + public ModelJoinParsingException(String message) { super(message); } /** * @see RuntimeException#RuntimeException(String, Throwable) */ - ModelJoinParsingException(String message, Throwable cause) { + public ModelJoinParsingException(String message, Throwable cause) { super(message, cause); } /** * @see RuntimeException#RuntimeException(Throwable) */ - ModelJoinParsingException(Throwable cause) { + public ModelJoinParsingException(Throwable cause) { super(cause); } } diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/AntlrBackedModelJoinParser.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/AntlrBackedModelJoinParser.java new file mode 100644 index 0000000000000000000000000000000000000000..86773781a112c27a910fca63acc710f9d50f331f --- /dev/null +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/AntlrBackedModelJoinParser.java @@ -0,0 +1,104 @@ +package org.rosi_project.model_sync.model_join.representation.parser.antlr; + +import java.io.File; +import java.io.IOException; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.TokenStream; +import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression; +import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParser; +import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinLexer; + +/** + * The {@code AntlrBackedModelJoinParser} reads a {@code ModelJoin} file according to a compiled + * {@code ANTLR} grammar and generates the matching {@link ModelJoinExpression} for the file. + * + * @author Rico Bergmann + * @see <a href="https://www.antlr.org/">ANTLR project</a> + */ +public class AntlrBackedModelJoinParser implements ModelJoinParser { + + @Nonnull + @Override + public Optional<ModelJoinExpression> read(@Nonnull File modelFile) { + try { + return Optional.of(invokeAntlr(modelFile)); + } catch (ModelJoinParsingException e) { + return Optional.empty(); + } + } + + @Nonnull + @Override + public ModelJoinExpression readOrThrow(@Nonnull File modelFile) { + return invokeAntlr(modelFile); + } + + /** + * Executes the whole ANTLR workflow. + * + * @throws ModelJoinParsingException if the workflow fails. Details on the error will be added + * to the exception. + */ + protected ModelJoinExpression invokeAntlr(@Nonnull File modelFile) { + CharStream modelStream = consumeCompleteModelFileContent(modelFile); + ModelJoinLexer lexer = initializeLexerBasedOn(modelStream); + TokenStream tokenStream = tokenize(lexer); + + org.rosi_project.model_sync.model_join.representation.parser.antlr.generated. // + ModelJoinParser modelJoinParser = initializeParserBasedOn(tokenStream); + + org.rosi_project.model_sync.model_join.representation.parser.antlr.generated. // + ModelJoinParser.ModeljoinContext modelJoinContext = modelJoinParser.modeljoin(); + + ModelJoinVisitor modelJoinVisitor = new ModelJoinVisitor(); + + return modelJoinVisitor.visitModeljoin(modelJoinContext); + } + + /** + * Converts the content of a {@code ModelJoin} file into an ANTLR input stream. + * + * @throws ModelJoinParsingException if the file may not be read + */ + @Nonnull + protected CharStream consumeCompleteModelFileContent(@Nonnull File modelFile) { + try { + return CharStreams.fromPath(modelFile.toPath()); + } catch (IOException e) { + throw new ModelJoinParsingException(e); + } + } + + /** + * Sets up a lexer for the given {@code ModelJoin} file. + */ + @Nonnull + protected ModelJoinLexer initializeLexerBasedOn(@Nonnull CharStream modelFile) { + return new ModelJoinLexer(modelFile); + } + + /** + * Executes the lexing step. + */ + @Nonnull + protected TokenStream tokenize(@Nonnull ModelJoinLexer lexer) { + return new CommonTokenStream(lexer); + } + + /** + * Sets up the parser. + */ + @Nonnull + protected org.rosi_project.model_sync.model_join.representation.parser.antlr.generated. // + ModelJoinParser initializeParserBasedOn(@Nonnull TokenStream tokens) { + return new org.rosi_project.model_sync.model_join.representation.parser.antlr.generated. // + ModelJoinParser(tokens); + } + +} + diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/JoinStatementVisitor.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/JoinStatementVisitor.java new file mode 100644 index 0000000000000000000000000000000000000000..a00d25b41693d9019379c34adb0349fb4b6870d8 --- /dev/null +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/JoinStatementVisitor.java @@ -0,0 +1,174 @@ +package org.rosi_project.model_sync.model_join.representation.parser.antlr; + +import javax.annotation.Nonnull; +import org.rosi_project.model_sync.model_join.representation.core.ClassResource; +import org.rosi_project.model_sync.model_join.representation.core.OCLConstraint; +import org.rosi_project.model_sync.model_join.representation.grammar.JoinExpression; +import org.rosi_project.model_sync.model_join.representation.grammar.JoinExpression.JoinType; +import org.rosi_project.model_sync.model_join.representation.grammar.NaturalJoinExpression; +import org.rosi_project.model_sync.model_join.representation.grammar.OuterJoinExpression; +import org.rosi_project.model_sync.model_join.representation.grammar.ThetaJoinExpression; +import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinBaseVisitor; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.JoinContext; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.NaturaljoinContext; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.OuterjoinContext; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.ThetajoinContext; +import org.rosi_project.model_sync.model_join.representation.util.JoinFactory; +import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.NaturalJoinBuilder; +import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.OuterJoinBuilder; +import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.ThetaJoinBuilder; + +/** + * The {@code JoinStatementVisitor} creates {@link JoinExpression} instances for a parsed {@code + * ModelJoin} file. + * + * @author Rico Bergmann + */ +public class JoinStatementVisitor extends ModelJoinBaseVisitor<JoinExpression> { + + private final KeepStatementVisitor keepStatementVisitor; + + /** + * Default constructor. Nothing special about it. + */ + JoinStatementVisitor() { + this.keepStatementVisitor = new KeepStatementVisitor(); + } + + @Override + public JoinExpression visitJoin(JoinContext ctx) { + JoinType joinType = determineJoinType(ctx); + return invokeJoinBuilderAccordingTo(joinType, ctx); + } + + /** + * Checks, which kind of join is specified in a specific {@code JoinContext}. + */ + private JoinType determineJoinType(@Nonnull JoinContext ctx) { + if (ctx.thetajoin() != null) { + return JoinType.THETA; + } else if (ctx.naturaljoin() != null) { + return JoinType.NATURAL; + } else if (ctx.outerjoin() != null) { + return JoinType.OUTER; + } else { + throw new ModelJoinParsingException("Unkown join type in join " + ctx); + } + } + + /** + * Generates a {@link JoinExpression} according to the {@link JoinType} of the given {@code + * JoinContext}. + */ + private JoinExpression invokeJoinBuilderAccordingTo(@Nonnull JoinType type, + @Nonnull JoinContext ctx) { + switch (type) { + case THETA: + return generateThetaJoin(ctx); + case NATURAL: + return generateNaturalJoin(ctx); + case OUTER: + return generateOuterJoin(ctx); + default: + throw new AssertionError(type); + } + } + + /** + * Creates the {@link ThetaJoinExpression} instance contained in a {@code JoinContext}. This + * method assumes that the provided {@code ctx} indeed contains a theta join. + */ + private ThetaJoinExpression generateThetaJoin(@Nonnull JoinContext ctx) { + ThetajoinContext thetajoinContext = ctx.thetajoin(); + + ClassResource left = ClassResource.fromQualifiedName(thetajoinContext.classres(0).getText()); + ClassResource right = ClassResource.fromQualifiedName(thetajoinContext.classres(1).getText()); + + ThetaJoinBuilder joinBuilder = JoinFactory.createNew() + .theta() + .join(left) + .with(right) + .as(ClassResource.fromQualifiedName(ctx.classres().getText())) + .where(OCLConstraint.of(thetajoinContext.oclcond().getText())); + + ctx.keepaggregatesexpr().stream() + .map(keepStatementVisitor::visitKeepaggregatesexpr) + .forEach(joinBuilder::keep); + ctx.keepattributesexpr().stream() + .map(keepStatementVisitor::visitKeepattributesexpr) + .forEach(joinBuilder::keep); + ctx.keepexpr().stream() + .map(keepStatementVisitor::visitKeepexpr) + .forEach(joinBuilder::keep); + + return joinBuilder.done(); + } + + /** + * Creates the {@link NaturalJoinExpression} instance contained in a {@code JoinContext}. This + * method assumes that the provided {@code ctx} indeed contains a natural join. + */ + private NaturalJoinExpression generateNaturalJoin(@Nonnull JoinContext ctx) { + NaturaljoinContext naturalJoinContext = ctx.naturaljoin(); + + ClassResource left = ClassResource.fromQualifiedName(naturalJoinContext.classres(0).getText()); + ClassResource right = ClassResource.fromQualifiedName(naturalJoinContext.classres(1).getText()); + + NaturalJoinBuilder joinBuilder = JoinFactory.createNew() + .natural() + .join(left) + .with(right) + .as(ClassResource.fromQualifiedName(ctx.classres().getText())); + + ctx.keepaggregatesexpr().stream() + .map(keepStatementVisitor::visitKeepaggregatesexpr) + .forEach(joinBuilder::keep); + ctx.keepattributesexpr().stream() + .map(keepStatementVisitor::visitKeepattributesexpr) + .forEach(joinBuilder::keep); + ctx.keepexpr().stream() + .map(keepStatementVisitor::visitKeepexpr) + .forEach(joinBuilder::keep); + + return joinBuilder.done(); + } + + /** + * Creates the {@link OuterJoinExpression} instance contained in a {@code JoinContext}. This + * method assumes that the provided {@code ctx} indeed contains an outer join. + */ + private OuterJoinExpression generateOuterJoin(@Nonnull JoinContext ctx) { + OuterjoinContext outerJoinContext = ctx.outerjoin(); + + ClassResource left = ClassResource.fromQualifiedName(outerJoinContext.classres(0).getText()); + ClassResource right = ClassResource.fromQualifiedName(outerJoinContext.classres(1).getText()); + + OuterJoinBuilder joinBuilder = JoinFactory.createNew() + .outer() + .join(left) + .with(right) + .as(ClassResource.fromQualifiedName(ctx.classres().getText())); + + if (outerJoinContext.leftouterjoin() != null) { + joinBuilder.leftOuter(); + } else if (outerJoinContext.rightouterjoin() != null) { + joinBuilder.rightOuter(); + } else { + throw new ModelJoinParsingException("Expected either 'left' or 'right' 'outer join'"); + } + + ctx.keepaggregatesexpr().stream() + .map(keepStatementVisitor::visitKeepaggregatesexpr) + .forEach(joinBuilder::keep); + ctx.keepattributesexpr().stream() + .map(keepStatementVisitor::visitKeepattributesexpr) + .forEach(joinBuilder::keep); + ctx.keepexpr().stream() + .map(keepStatementVisitor::visitKeepexpr) + .forEach(joinBuilder::keep); + + return joinBuilder.done(); + } + +} diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/KeepStatementVisitor.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/KeepStatementVisitor.java new file mode 100644 index 0000000000000000000000000000000000000000..a7e6397b28bfeed1bebdf88779a8697b126af397 --- /dev/null +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/KeepStatementVisitor.java @@ -0,0 +1,293 @@ +package org.rosi_project.model_sync.model_join.representation.parser.antlr; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +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.core.RelativeAttribute; +import org.rosi_project.model_sync.model_join.representation.grammar.KeepAggregateExpression; +import org.rosi_project.model_sync.model_join.representation.grammar.KeepAggregateExpression.AggregationType; +import org.rosi_project.model_sync.model_join.representation.grammar.KeepAttributesExpression; +import org.rosi_project.model_sync.model_join.representation.grammar.KeepExpression; +import org.rosi_project.model_sync.model_join.representation.grammar.KeepReferenceExpression; +import org.rosi_project.model_sync.model_join.representation.grammar.KeepSubTypeExpression; +import org.rosi_project.model_sync.model_join.representation.grammar.KeepSuperTypeExpression; +import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinBaseVisitor; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.AttrresContext; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepaggregatesexprContext; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepattributesexprContext; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepexprContext; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepincomingexprContext; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepoutgoingexprContext; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepsubtypeexprContext; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepsupertypeexprContext; + +/** + * The {@code KeepStatementVisitor} creates {@link KeepExpression} instances from a parsed {@code + * ModelJoin} file. + * + * @author Rico Bergmann + */ +public class KeepStatementVisitor extends ModelJoinBaseVisitor<KeepExpression> { + + /** + * A simple enumeration of the possible keep statements for compound statements as defined in the + * {@code ModelJoin} ANTLR grammar. + */ + private enum KeepExprType { + + /** + * The {@code KEEP_TYPE_EXPR} subsumes both {@code keep subtype} as well as {@code keep + * supertype} expressions. + */ + KEEP_TYPE_EXPR, + + /** + * The {@code KEEP_INCOMING_EXPR} corresponds to a {@code keep incoming reference} statement. + */ + KEEP_INCOMING_EXPR, + + /** + * The {@code KEEP_OUTGOING_EXPR} corresponds to a {@code keep outgoing reference} statement. + */ + KEEP_OUTGOING_EXPR + + } + + @Override + public KeepExpression visitKeepattributesexpr(KeepattributesexprContext ctx) { + return new KeepAttributesExpression( + ctx.attrres().stream() + .map(AttrresContext::getText) + .map(AttributePath::fromQualifiedPath) + .collect(ArrayList::new, ArrayList::add, ArrayList::addAll) + ); + + } + + @Override + public KeepExpression visitKeepaggregatesexpr(KeepaggregatesexprContext ctx) { + String rawAggregationType = ctx.aggrtype().getText(); + AggregationType aggregationType = AggregationType.valueOf(rawAggregationType.toUpperCase()); + + return new KeepAggregateExpression( + aggregationType /* SUM / MAX / MIN etc */, + RelativeAttribute.of(ctx.relattr().getText()) /* the attribute in the target relation */, + AttributePath.fromQualifiedPath(ctx.classres().getText()) /* the source attribute */, + AttributePath.fromQualifiedPath(ctx.attrres().getText()) /* name in the new ModelJoin */ + ); + } + + @Override + public KeepExpression visitKeepexpr(KeepexprContext ctx) { + KeepExprType keepExprType = determineExpressionTypeFor(ctx); + + switch (keepExprType) { + case KEEP_TYPE_EXPR: + return buildKeepTypeExpressionFor(ctx); + case KEEP_INCOMING_EXPR: + return buildKeepIncomingReferenceExpressionFor(ctx); + case KEEP_OUTGOING_EXPR: + return buildKeepOutgoingReferenceExpressionFor(ctx); + default: + throw new AssertionError(keepExprType); + } + } + + /** + * Checks, which kind of expression is specified in a specific {@code KeepexprContext}. + */ + private KeepExprType determineExpressionTypeFor(KeepexprContext ctx) { + + /* + * From the ANTLR grammar we know that exactly one of the IF cases has to hold + */ + if (ctx.keeptypeexpr() != null) { + return KeepExprType.KEEP_TYPE_EXPR; + } else if (ctx.keepincomingexpr() != null) { + return KeepExprType.KEEP_INCOMING_EXPR; + } else if (ctx.keepoutgoingexpr() != null) { + return KeepExprType.KEEP_OUTGOING_EXPR; + } else { + throw new ModelJoinParsingException( + "Expected either 'keep reference' or 'keep type' expression: " + ctx); + } + } + + /** + * Creates the appropriate {@link KeepExpression} for a {@code KeepexprContext}. + */ + private KeepExpression buildKeepTypeExpressionFor(KeepexprContext ctx) { + if (ctx.keeptypeexpr().keepsubtypeexpr() != null) { + return buildKeepSubtypeExpressionFor(ctx); + } else if (ctx.keeptypeexpr().keepsupertypeexpr() != null) { + return buildKeepSupertypeExpressionFor(ctx); + } else { + throw new ModelJoinParsingException( + "Expected either 'keep supertype' or 'keep subtype' expression: " + ctx); + } + } + + /** + * Creates the {@link KeepReferenceExpression} instance contained in a {@code KeepexprContext}. + * This method assumes that the provided {@code ctx} indeed contains a keep incoming reference + * statement. + */ + private KeepReferenceExpression buildKeepIncomingReferenceExpressionFor(KeepexprContext ctx) { + KeepincomingexprContext incomingCtx = ctx.keepincomingexpr(); + + AttributePath attributeToKeep = AttributePath + .fromQualifiedPath(incomingCtx.attrres().getText()); + + List<KeepExpression> nestedExpressions = ctx.keepattributesexpr().stream() + .map(this::visitKeepattributesexpr) + .collect(Collectors.toList()); + + nestedExpressions.addAll(ctx.keepaggregatesexpr().stream() + .map(this::visitKeepaggregatesexpr) + .collect(Collectors.toList()) + ); + + nestedExpressions.addAll(ctx.keepexpr().stream() + .map(this::visitKeepexpr) + .collect(Collectors.toList()) + ); + + KeepReferenceExpression.KeepReferenceBuilder expressionBuilder = KeepReferenceExpression.keep() + .incoming(attributeToKeep); + + ClassResource targetClass; + if (incomingCtx.classres() != null) { + targetClass = ClassResource.fromQualifiedName(incomingCtx.classres().getText()); + } else { + targetClass = attributeToKeep.getContainingClass(); + } + + expressionBuilder.as(targetClass); + nestedExpressions.forEach(expressionBuilder::keep); + + return expressionBuilder.buildExpression(); + } + + /** + * Creates the {@link KeepReferenceExpression} instance contained in a {@code KeepexprContext}. + * This method assumes that the provided {@code ctx} indeed contains a keep outgoing reference + * statement. + */ + private KeepReferenceExpression buildKeepOutgoingReferenceExpressionFor(KeepexprContext ctx) { + KeepoutgoingexprContext outgoingCtx = ctx.keepoutgoingexpr(); + + AttributePath attributeToKeep = AttributePath + .fromQualifiedPath(outgoingCtx.attrres().getText()); + + List<KeepExpression> nestedExpressions = ctx.keepattributesexpr().stream() + .map(this::visitKeepattributesexpr) + .collect(Collectors.toList()); + + nestedExpressions.addAll(ctx.keepaggregatesexpr().stream() + .map(this::visitKeepaggregatesexpr) + .collect(Collectors.toList()) + ); + + nestedExpressions.addAll(ctx.keepexpr().stream() + .map(this::visitKeepexpr) + .collect(Collectors.toList()) + ); + + KeepReferenceExpression.KeepReferenceBuilder expressionBuilder = KeepReferenceExpression.keep() + .outgoing(attributeToKeep); + + ClassResource targetClass; + if (outgoingCtx.classres() != null) { + targetClass = ClassResource.fromQualifiedName(outgoingCtx.classres().getText()); + } else { + targetClass = attributeToKeep.getContainingClass(); + } + + expressionBuilder.as(targetClass); + nestedExpressions.forEach(expressionBuilder::keep); + + return expressionBuilder.buildExpression(); + } + + /** + * Creates the {@link KeepSubTypeExpression} instance contained in a {@code KeepexprContext}. This + * method assumes that the provided {@code ctx} indeed contains a keep subtype statement. + */ + private KeepSubTypeExpression buildKeepSubtypeExpressionFor(KeepexprContext ctx) { + KeepsubtypeexprContext subtypeCtx = ctx.keeptypeexpr().keepsubtypeexpr(); + + ClassResource typeToKeep = ClassResource.fromQualifiedName(subtypeCtx.classres(0).getText()); + + List<KeepExpression> nestedExpressions = ctx.keepattributesexpr().stream() + .map(this::visitKeepattributesexpr) + .collect(Collectors.toList()); + + nestedExpressions.addAll(ctx.keepaggregatesexpr().stream() + .map(this::visitKeepaggregatesexpr) + .collect(Collectors.toList()) + ); + + nestedExpressions.addAll(ctx.keepexpr().stream() + .map(this::visitKeepexpr) + .collect(Collectors.toList()) + ); + + KeepSubTypeExpression.KeepSubTypeBuilder expressionBuilder = KeepSubTypeExpression.keep() + .subtype(typeToKeep); + + ClassResource targetClass; + if (subtypeCtx.classres() != null) { + targetClass = ClassResource.fromQualifiedName(subtypeCtx.classres(1).getText()); + } else { + targetClass = typeToKeep; + } + + expressionBuilder.as(targetClass); + nestedExpressions.forEach(expressionBuilder::keep); + + return expressionBuilder.buildExpression(); + } + + /** + * Creates the {@link KeepSuperTypeExpression} instance contained in a {@code KeepexprContext}. This + * method assumes that the provided {@code ctx} indeed contains a keep supertype statement. + */ + private KeepSuperTypeExpression buildKeepSupertypeExpressionFor(KeepexprContext ctx) { + KeepsupertypeexprContext supertypeCtx = ctx.keeptypeexpr().keepsupertypeexpr(); + + ClassResource typeToKeep = ClassResource.fromQualifiedName(supertypeCtx.classres(0).getText()); + + List<KeepExpression> nestedExpressions = ctx.keepattributesexpr().stream() + .map(this::visitKeepattributesexpr) + .collect(Collectors.toList()); + + nestedExpressions.addAll(ctx.keepaggregatesexpr().stream() + .map(this::visitKeepaggregatesexpr) + .collect(Collectors.toList()) + ); + + nestedExpressions.addAll(ctx.keepexpr().stream() + .map(this::visitKeepexpr) + .collect(Collectors.toList()) + ); + + KeepSuperTypeExpression.KeepSuperTypeBuilder expressionBuilder = KeepSuperTypeExpression.keep() + .supertype(typeToKeep); + + ClassResource targetClass; + if (supertypeCtx.classres() != null) { + targetClass = ClassResource.fromQualifiedName(supertypeCtx.classres(1).getText()); + } else { + targetClass = typeToKeep; + } + + expressionBuilder.as(targetClass); + nestedExpressions.forEach(expressionBuilder::keep); + + return expressionBuilder.buildExpression(); + } + +} diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/ModelJoinVisitor.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/ModelJoinVisitor.java new file mode 100644 index 0000000000000000000000000000000000000000..f1883fa77954a74ed677d8a5b6cc358ca2721946 --- /dev/null +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/ModelJoinVisitor.java @@ -0,0 +1,28 @@ +package org.rosi_project.model_sync.model_join.representation.parser.antlr; + +import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinBaseVisitor; +import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.ModeljoinContext; +import org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder; + +/** + * The {@code ModelJoinVisitor} creates a {@link ModelJoinExpression} instances from a parsed {@code + * ModelJoin} file. + * + * @author Rico Bergmann + */ +public class ModelJoinVisitor extends ModelJoinBaseVisitor<ModelJoinExpression> { + + @Override + public ModelJoinExpression visitModeljoin(ModeljoinContext ctx) { + JoinStatementVisitor joinVisitor = new JoinStatementVisitor(); + ModelJoinBuilder modelJoinBuilder = ModelJoinBuilder.createNewModelJoin(); + + ctx.join().stream() // + .map(joinVisitor::visitJoin) // + .forEach(modelJoinBuilder::add); + + return modelJoinBuilder.build(); + } + +} diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/DefaultModelJoinParser.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/DefaultModelJoinParser.java new file mode 100644 index 0000000000000000000000000000000000000000..25f4b9ce3871862e570befcb4f28ad761f467345 --- /dev/null +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/DefaultModelJoinParser.java @@ -0,0 +1,187 @@ +package org.rosi_project.model_sync.model_join.representation.parser.legacy; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Optional; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression; +import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParser; +import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException; +import org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder; + +/** + * The {@code DefaultModelJoinParser} reads a {@code ModelJoin} file from disk and creates matching + * representation for it. + * + * @author Rico Bergmann + */ +@Deprecated +public class DefaultModelJoinParser { + + /** + * Converts the {@code ModelJoin} file into a {@link ModelJoinExpression}, wrapped into an {@code + * Optional}. If parsing fails, an empty {@code Optional} will be returned. + */ + @Nonnull + public static Optional<ModelJoinExpression> read(@Nonnull File modelFile) { + DefaultModelJoinParser parser = new DefaultModelJoinParser(modelFile, ErrorReportingStrategy.OPTIONAL); + return parser.run(); + } + + /** + * Converts the {@code ModelJoin} file into a {@link ModelJoinExpression}. If parsing fails, a + * {@link ModelJoinParsingException} will be thrown. + * + * @throws ModelJoinParsingException if the model may not be parsed for some reason. + */ + @Nonnull + public static ModelJoinExpression readOrThrow(@Nonnull File modelFile) { + DefaultModelJoinParser parser = new DefaultModelJoinParser(modelFile, ErrorReportingStrategy.EXCEPTION); + Optional<ModelJoinExpression> result = parser.run(); + + if (result.isPresent()) { + return result.get(); + } else { + /* + * Theoretically, the parser should throw the exception by itself, so this code should never + * actually run. It is merely here for safety purposes (unchecked access of Optional.get()) + */ + throw new ModelJoinParsingException("Result not present but no exception was thrown either"); + } + } + + /** + * Indicates what should happen if parsing fails. + */ + enum ErrorReportingStrategy { + + /** + * On failure an empty {@code Optional} should be returned. + */ + OPTIONAL, + + /** + * On failure an {@link ModelJoinParsingException} should be thrown. + */ + EXCEPTION + } + + private static final Pattern EMPTY_LINE = Pattern.compile("^\\s*$"); + private static final Pattern IMPORT_STATEMENT = Pattern.compile("^import .*"); + private static final Pattern TARGET_STATEMENT = Pattern.compile("^target .*"); + private static final Pattern JOIN_STATEMENT = Pattern + .compile("^(((left|right) outer)|theta|natural) join .*"); + + @Nonnull + private final File modelFile; + + @Nonnull + private final ErrorReportingStrategy errorReportingStrategy; + + /** + * Full constructor. + * + * @param modelFile the file to parse + * @param errorReportingStrategy what to do in case an error occurs. + */ + private DefaultModelJoinParser(@Nonnull File modelFile, + @Nonnull ErrorReportingStrategy errorReportingStrategy) { + this.modelFile = modelFile; + this.errorReportingStrategy = errorReportingStrategy; + } + + /** + * Creates the {@code ModelJoin} representation from the {@code modelFile}. + * <p> + * Depending on the selected {@code errorReportingStrategy} either an Exception will be thrown, or + * an empty {@code Optional} will be returned if something goes wrong. + */ + private Optional<ModelJoinExpression> run() { + ModelJoinExpression resultingModel; + try (FileInputStream modelInputStream = new FileInputStream(modelFile); + InputStreamReader modelInputReader = new InputStreamReader(modelInputStream); + BufferedReader bufferedModelReader = new BufferedReader(modelInputReader)) { + + ModelJoinBuilder modelJoinBuilder = ModelJoinBuilder.createNewModelJoin(); + readModelFileAndPopulateBuilder(bufferedModelReader, modelJoinBuilder); + resultingModel = modelJoinBuilder.build(); + + } catch (ModelJoinParsingException e) { + switch (errorReportingStrategy) { + case OPTIONAL: + return Optional.empty(); + case EXCEPTION: + throw e; + default: + throw new AssertionError(errorReportingStrategy); + } + } catch (IOException e) { + switch (errorReportingStrategy) { + case OPTIONAL: + return Optional.empty(); + case EXCEPTION: + throw new ModelJoinParsingException(e); + default: + throw new AssertionError(errorReportingStrategy); + } + } + return Optional.of(resultingModel); + } + + /** + * Reads the {@code modelFile} and constructs the corresponding {@code ModelJoin} on the fly. + * <p> + * On error, an {@code IOException} or an {@link ModelJoinParsingException} will be thrown. + * + * @throws ModelJoinParsingException if the model file's content are malformed or another + * component failed + * @throws IOException if the model file might not be read any further + */ + private void readModelFileAndPopulateBuilder(BufferedReader modelReader, + ModelJoinBuilder modelBuilder) throws IOException { + String currentLine; + while ((currentLine = modelReader.readLine()) != null) { + if (lineShouldBeSkipped(currentLine)) { + // the line should be skipped. So we do nothing. + continue; + } else if (lineStartsJoinDeclaration(currentLine)) { + // a new join is being declared. Delegate to the join parser to handle the rest. + JoinParser joinParser = new JoinParser(currentLine, modelReader); + modelBuilder.add(joinParser.run()); + } else { + // we do not know what to do with the current line. Should abort. + throw new ModelJoinParsingException("Unexpected line: '" + currentLine + "'"); + } + } + } + + /** + * Checks, whether the parser should consider the given {@code line} any further, or just ignore + * it. + * <p> + * A line should be skipped, iff. + * <ul> + * <li>It only consists of whitespace characters</li> + * <li>It is an {@code import statement} (as those are not yet considered in the current {@code + * ModelJoin} abstraction)</li> + * <li>It is a {@code target statement} (as those are not yet considered in the current {@code + * ModelJoin} abstraction)</li> + * </ul> + */ + private boolean lineShouldBeSkipped(String line) { + return EMPTY_LINE.matcher(line).matches() // + || IMPORT_STATEMENT.matcher(line).matches() // + || TARGET_STATEMENT.matcher(line).matches(); + } + + /** + * Checks, whether the given {@code line} is the beginning of a {@code join statement}. + */ + private boolean lineStartsJoinDeclaration(String line) { + return JOIN_STATEMENT.matcher(line).matches(); + } +} diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/JoinParser.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/JoinParser.java similarity index 98% rename from src/main/java/org/rosi_project/model_sync/model_join/representation/parser/JoinParser.java rename to src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/JoinParser.java index 36b2de29fe0b005b6fa9923553013237cae45066..41f40e980bdfe7e2a8d9ec95a01a720e141c8ff7 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/JoinParser.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/JoinParser.java @@ -1,4 +1,4 @@ -package org.rosi_project.model_sync.model_join.representation.parser; +package org.rosi_project.model_sync.model_join.representation.parser.legacy; import java.io.BufferedReader; import java.io.IOException; @@ -10,6 +10,7 @@ import org.rosi_project.model_sync.model_join.representation.core.ClassResource; import org.rosi_project.model_sync.model_join.representation.core.OCLConstraint; import org.rosi_project.model_sync.model_join.representation.grammar.JoinExpression; import org.rosi_project.model_sync.model_join.representation.grammar.KeepExpression; +import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException; import org.rosi_project.model_sync.model_join.representation.util.JoinFactory; import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.NaturalJoinBuilder; import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.OuterJoinBuilder; @@ -19,7 +20,7 @@ import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.Th * The {@code JoinParser} takes care of reading an individual {@code join statement} and creates the * corresponding {@link JoinExpression}. * <p> - * This class is primarily intended to be created and invoked by the {@link ModelJoinParser} only as + * This class is primarily intended to be created and invoked by the {@link DefaultModelJoinParser} only as * will modify the state of that as well. * * @author Rico Bergmann @@ -50,7 +51,7 @@ class JoinParser { /** * Full constructor. * - * @param startingLine the line that was last read by the {@link ModelJoinParser} and should + * @param startingLine the line that was last read by the {@link DefaultModelJoinParser} and should * therefore become the starting point for {@code this} parser. * @param modelReader the reader that provides access to the following lines in the {@code * ModelJoin} file currently being read. diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/KeepParser.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/KeepParser.java similarity index 97% rename from src/main/java/org/rosi_project/model_sync/model_join/representation/parser/KeepParser.java rename to src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/KeepParser.java index 1ea6db9a87f2f63ee7aec6959d1ead6e13690e43..ee20f8827e2e537ee135c17b7f5728f993eef2f9 100644 --- a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/KeepParser.java +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/KeepParser.java @@ -1,4 +1,4 @@ -package org.rosi_project.model_sync.model_join.representation.parser; +package org.rosi_project.model_sync.model_join.representation.parser.legacy; import java.io.BufferedReader; import java.io.IOException; @@ -12,6 +12,7 @@ import javax.annotation.Nonnull; import org.rosi_project.model_sync.model_join.representation.core.AttributePath; import org.rosi_project.model_sync.model_join.representation.grammar.KeepAttributesExpression; import org.rosi_project.model_sync.model_join.representation.grammar.KeepExpression; +import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException; /** * The {@code KeepParser} takes care of reading the {@code keep statements} and creating the diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/util/Assert.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/util/Assert.java new file mode 100644 index 0000000000000000000000000000000000000000..d72ecca9b380b86828c5134808cef2e48bc46cd8 --- /dev/null +++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/util/Assert.java @@ -0,0 +1,36 @@ +package org.rosi_project.model_sync.model_join.representation.util; + +import javax.annotation.Nonnull; + +/** + * Utility class to provide a number of useful assertion mechanisms. + * <p> + * On failure, an {@code AssertionError} with a dedicated message will be thrown. + * + * @author Rico Bergmann + */ +public class Assert { + + /** + * Ensures that an object is not {@code null}. + */ + public static void notNull(Object obj, @Nonnull String failureMessage) { + if (obj == null) { + throw new AssertionError(failureMessage); + } + } + + /** + * Ensures that none of the arguments is {@code null}. + */ + public static void noNullArguments(@Nonnull String failureMessage, Object... arguments) { + if (arguments == null) { + throw new AssertionError("Vararg arguments may not be null"); + } + for (Object arg : arguments) { + if (arg == null) { + throw new AssertionError(failureMessage); + } + } + } +}