Skip to content
Snippets Groups Projects
Commit 575028b6 authored by Chrissi's avatar Chrissi
Browse files

first commit with copy of all antlr parts from the code generator

parent a43bef41
No related branches found
No related tags found
No related merge requests found
Showing
with 2013 additions and 0 deletions
#### joe made this: http://goel.io/joe
#### scala ####
*.class
*.log
#### sbt ####
# Simple Build Tool
# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/
.history
.cache
.lib/
#### jetbrains ####
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
#### java ####
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
#### eclipse ####
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
#### Custom (user defined) insertions ####
.idea/
.classpath
.project
output/
doc/
[lL]ocal[tT]est*
# ANTLR
gen/
import com.simplytyped.Antlr4Plugin.autoImport.antlr4PackageName
import sbt.Keys.{libraryDependencies, scalacOptions, version}
val emfcommonVersion = "2.12.0"
val emfecoreVersion = "2.12.0"
val scoptVersion = "3.7.0"
val liftVersion = "3.3.0"
javacOptions ++= Seq("-encoding", "UTF-8")
lazy val generator = (project in file("."))
.settings(
name := "CodeGenerator",
version := "0.1",
scalaVersion := "2.12.6",
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.scala-lang" % "scala-compiler" % scalaVersion.value,
"org.eclipse.emf" % "org.eclipse.emf.common" % emfcommonVersion,
"org.eclipse.emf" % "org.eclipse.emf.ecore" % emfecoreVersion,
"com.github.scopt" %% "scopt" % scoptVersion,
"net.liftweb" %% "lift-json" % liftVersion,
"org.junit.platform" % "junit-platform-runner" % "1.0.0" % "test",
"org.junit.jupiter" % "junit-jupiter-engine" % "5.0.0" % "test",
"org.junit.vintage" % "junit-vintage-engine" % "4.12.0" % "test",
"org.assertj" % "assertj-core" % "3.12.2" % "test",
"net.aichler" % "jupiter-interface" % JupiterKeys.jupiterVersion.value % Test
),
scalacOptions ++= Seq(
"-language:implicitConversions"
),
).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
sbt.version = 1.2.7
resolvers += Resolver.jcenterRepo
addSbtPlugin("net.aichler" % "sbt-jupiter-interface" % "0.8.2")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0")
addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.2")
/*
* 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*
keepcalculatedexpr*
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 ;
keepcalculatedexpr : KEEP CALCULATED ATTRIBUTE oclcond AS (typedattrres | attrres) ;
keepexpr : (keeptypeexpr | keepoutgoingexpr | keepincomingexpr)
( OPENCURLYBRAKET
keepattributesexpr*
keepaggregatesexpr*
keepcalculatedexpr*
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)+ ;
typedattrres : attrres COLON WORD ;
relattr : WORD ;
oclcond : (WORD | NUMBER | specialchar | anytoken | WHITESPACE | NEWLINE)+;
aggrtype : SUM | AVG | MIN | MAX | SIZE ;
specialchar : DOT
| OPENBRAKET
| CLOSEDBRAKET
| OPENCURLYBRAKET
| CLOSEDCURLYBRAKET
| COMMA
| UNDERSCORE
| SPECIALCHAR ;
// anytoken matches all possible tokens in case their content may appear as part of some regular
// text as well
anytoken : OPENBRAKET
| CLOSEDBRAKET
| OPENCURLYBRAKET
| CLOSEDCURLYBRAKET
| DOT
| COLON
| COMMA
| UNDERSCORE
| SPECIALCHAR
| JOIN
| NATURAL
| THETA
| WHERE
| OUTER
| RIGHT
| LEFT
| WITH
| AS
| KEEP
| ATTRIBUTES
| ATTRIBUTE
| AGGREGATE
| CALCULATED
| SUPERTYPE
| SUBTYPE
| OUTGOING
| INCOMING
| TYPE
| OVER
| SUM
| AVG
| MIN
| MAX
| SIZE ;
/*
* Lexer
*/
fragment LOWERCASE : [a-z] ;
fragment UPPERCASE : [A-Z] ;
fragment ANYCASE : LOWERCASE | UPPERCASE ;
fragment DIGIT : [0-9] ;
OPENBRAKET : '(' ;
CLOSEDBRAKET : ')' ;
OPENCURLYBRAKET : '{' ;
CLOSEDCURLYBRAKET : '}' ;
DOT : '.' ;
COLON : ':' ;
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' ;
ATTRIBUTE : 'attribute' ;
AGGREGATE : 'aggregate' ;
CALCULATED : 'calculated' ;
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 ;
package org.rosi_project.model_sync.model_join.representation.core;
import java.util.Objects;
import javax.annotation.Nonnull;
/**
* An {@code AttributePath} represents a single attribute of some model class.
* <p>
* As <em>ModelJoin</em> does not care about types at a conceptual level, no type information is
* stored.
*
* @author Rico Bergmann
*/
public class AttributePath {
private static final String CLASS_ATTRIBUTE_DELIMITER = ".";
/**
* Generates a new {@code path} given the owning class as well as the attribute's name.
*/
@Nonnull
public static AttributePath from(
@Nonnull ClassResource containingClass,
@Nonnull String attributeName) {
return new AttributePath(containingClass, attributeName);
}
/**
* Generates a new {@code path} given the owning class as well as the attribute.
*/
@Nonnull
public static AttributePath from(
@Nonnull ClassResource containingClass,
@Nonnull RelativeAttribute attribute) {
return new AttributePath(containingClass, attribute);
}
public static AttributePath from(String containingClass, String attributeName) {
return new AttributePath(containingClass, attributeName);
}
/**
* Generates a new {@code AttributePath} by splitting up the {@code qualifiedPath} into the
* {@link ClassResource} and attribute portion. Therefore the {@code qualifiedPath} is expected
* to adhere to the following scheme: {@code [package].class.attribute}.
*/
@Nonnull
public static AttributePath fromQualifiedPath(@Nonnull String qualifiedPath) {
final int splitPos = qualifiedPath.lastIndexOf(CLASS_ATTRIBUTE_DELIMITER);
if (splitPos < 0) {
throw new IllegalArgumentException("Missing class portion on '" + qualifiedPath + "'");
}
String classResourcePortion = qualifiedPath.substring(0, splitPos);
String attributePortion = qualifiedPath.substring(splitPos + 1);
return new AttributePath(ClassResource.fromQualifiedName(classResourcePortion), attributePortion);
}
@Nonnull
private final ClassResource containingClass;
@Nonnull
private final RelativeAttribute attribute;
public AttributePath(String containingClass, String attributeName) {
this.containingClass = ClassResource.fromQualifiedName(containingClass);
this.attribute = RelativeAttribute.of(attributeName);
}
/**
* Full constructor, parsing the attribute's name into a valid {@link RelativeAttribute}.
*
* @param containingClass the class which owns the attribute
* @param attributeName the attribute's name
*/
public AttributePath(@Nonnull ClassResource containingClass, @Nonnull String attributeName) {
this.containingClass = containingClass;
this.attribute = RelativeAttribute.of(attributeName);
}
/**
* Full constructor.
*
* @param containingClass the class which owns the attribute
* @param attribute the attribute's name
*/
public AttributePath(@Nonnull ClassResource containingClass, @Nonnull RelativeAttribute attribute) {
this.containingClass = containingClass;
this.attribute = attribute;
}
/**
* Creates a new {@code AttributePath} by copying another path.
*/
protected AttributePath(@Nonnull AttributePath other) {
this.containingClass = other.getContainingClass();
this.attribute = other.getAttribute();
}
/**
* Provides the class which owns {@code this} attribute.
*/
@Nonnull
public ClassResource getContainingClass() {
return containingClass;
}
/**
* Provides {@code this} attribute as a {@link RelativeAttribute}.
*/
@Nonnull
public RelativeAttribute getAttribute() {
return attribute;
}
/**
* Provides the name of {@code this} attribute.
*/
@Nonnull
public String getAttributeName() {
return attribute.getAttributeName();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AttributePath)) {
return false;
}
AttributePath that = (AttributePath) o;
return containingClass.equals(that.containingClass) &&
attribute.equals(that.attribute);
}
@Override
public int hashCode() {
return Objects.hash(containingClass, attribute);
}
@Override
public String toString() {
return containingClass.toString() + "." + attribute;
}
}
package org.rosi_project.model_sync.model_join.representation.core;
import javax.annotation.Nonnull;
/**
* A {@code ClassResource} represents a fully-qualified reference to some class to be used in a
* join. This class merely exist to emphasize that some resource actually represents a class and
* thus provides a nicer (i.e. better named) interface to query for the resource's fields.
* <p>
* In difference to a {@link ResourcePath}, instances of this class will drop the {@code .java}
* ending of the file name if referenced from the file system.
*
* @author Rico Bergmann
*/
public class ClassResource extends ResourcePath {
/**
* Generates a new {@code path} given the path to its containing <em>package</em> and the name of
* the class which should be referenced.
*/
@Nonnull
public static ClassResource from(@Nonnull String packagePath, @Nonnull String className) {
return new ClassResource(packagePath, className);
}
/**
* @see ResourcePath#parse(String)
*/
@Nonnull
public static ClassResource fromQualifiedName(@Nonnull String qualifiedClass) {
/*
* A class resource is just a better representation of a class (in difference to just _some_
* kind of resource) but works just the same - thus we will re-use all the parsing logic from
* the ResourcePath
*/
ResourcePath parsedResource = ResourcePath.parse(qualifiedClass);
return new ClassResource(parsedResource.resourcePath, parsedResource.resourceName);
}
/**
* @see ResourcePath#parseAsFileSystemPath(String)
*/
@Nonnull
public static ClassResource fromFileSystem(@Nonnull String fullPath) {
/*
* A class resource is just a better representation of a class (in difference to just _some_
* kind of resource) but works just the same - thus we will re-use all the parsing logic from
* the ResourcePath
*/
ResourcePath parsedResource = ResourcePath.parseAsFileSystemPath(dropFileTypeEnding(fullPath));
return new ClassResource(parsedResource.resourcePath, parsedResource.resourceName);
}
/**
* Deletes the {@code .java} ending from a resource name.
*/
private static String dropFileTypeEnding(String fullPath) {
return fullPath.replaceAll("\\.java$", "");
}
/**
* Full constructor.
*
* @param containingPackage the path to the package to which the class belongs
* @param className the name of the class
*/
public ClassResource(@Nonnull String containingPackage, @Nonnull String className) {
super(containingPackage, className);
}
/**
* Provides the name of the class represented by {@code this} resource.
*/
@Nonnull
public String getClassName() {
return resourceName;
}
/**
* Provides the package that contains the class represented by {@code this} resource.
*/
@Nonnull
public String getPackage() {
return resourcePath;
}
}
package org.rosi_project.model_sync.model_join.representation.core;
import javax.annotation.Nonnull;
/**
* An {@code OCLConstraint} specifies some predicate on (possibly multiple) models that have to hold
* under certain conditions.
* <p>
* This class is a specialization of an arbitrary {@link OCLStatement} for statements, that
* evaluate to a boolean values.
*
* @author Rico Bergmann
*/
public class OCLConstraint extends OCLStatement {
/**
* Generates a new constraint with the given content.
*/
@Nonnull
public static OCLConstraint of(@Nonnull String content) {
return new OCLConstraint(content);
}
/**
* Full constructor.
*
* @param content the actual constraint
*/
public OCLConstraint(@Nonnull String content) {
super(content);
}
}
package org.rosi_project.model_sync.model_join.representation.core;
import java.util.Objects;
import javax.annotation.Nonnull;
/**
* An {@code OCLStatement} is a simple expression written in the {@code Object Constraint Language}.
* <p>
* This class simply wraps an arbitrary OCL expression. No further checks or the like are performed.
*
* @author Rico Bergmann
* @see <a href="https://www.omg.org/spec/OCL/">OMG OCL specification</a>
*/
public class OCLStatement {
/**
* Creates a new statement with the given {@code content}.
*/
@Nonnull
public static OCLStatement of(@Nonnull String content) {
return new OCLStatement(content);
}
@Nonnull
protected final String content;
/**
* Full constructor.
*
* @param content the actual expression
*/
public OCLStatement(@Nonnull String content) {
this.content = content;
}
/**
* Provides the actual statement as a {@code String}.
*/
public String get() {
return content;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
OCLStatement that = (OCLStatement) o;
return content.equals(that.content);
}
@Override
public int hashCode() {
return Objects.hash(content);
}
@Override
public String toString() {
return content;
}
}
package org.rosi_project.model_sync.model_join.representation.core;
import java.util.Objects;
import javax.annotation.Nonnull;
/**
* A {@code RelativeAttribute} represents a member of some class.
* <p>
* In difference to the {@link AttributePath} this class does not retain any information about the
* class to which it belongs.
*
* @author Rico Bergmann
*/
public class RelativeAttribute {
/**
* Extracts the attribute from a qualified {@link AttributePath}.
*/
@Nonnull
public static RelativeAttribute from(@Nonnull AttributePath attributePath) {
return new RelativeAttribute(attributePath.getAttributeName());
}
/**
* Generates a new {@code RelativeAttribute} with the given name.
*/
@Nonnull
public static RelativeAttribute of(@Nonnull String attributeName) {
if (!attributeName.matches(ATTRIBUTE_NAME_REGEX)) {
throw new IllegalArgumentException("Not a valid attribute name: " + attributeName);
}
return new RelativeAttribute(attributeName);
}
private static final String ATTRIBUTE_NAME_REGEX = "^[a-zA-Z_]\\w*$";
@Nonnull
private final String attributeName;
/**
* Full constructor.
*
* @param attributeName the name of the attribute to generate
*/
public RelativeAttribute(@Nonnull String attributeName) {
this.attributeName = attributeName;
}
/**
* Provides the name of the represented attribute.
*/
@Nonnull
public String getAttributeName() {
return attributeName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RelativeAttribute that = (RelativeAttribute) o;
return attributeName.equals(that.attributeName);
}
@Override
public int hashCode() {
return Objects.hash(attributeName);
}
@Override
public String toString() {
return attributeName;
}
}
package org.rosi_project.model_sync.model_join.representation.core;
import java.io.File;
import java.util.Objects;
import javax.annotation.Nonnull;
/**
* A {@code ResourcePath} represents a fully-qualified name of some model element to be used in a
* join.
*
* @author Rico Bergmann
*/
public class ResourcePath {
/**
* Generates a new {@code path} given the path to its containing <em>package</em> and the name of
* the resource which should be referenced.
*/
@Nonnull
public static ResourcePath from(@Nonnull String packagePath, @Nonnull String resourceName) {
return new ResourcePath(packagePath, resourceName);
}
/**
* Generates a new {@code path} given its fully-qualified name.
*
* @param qualifiedResourcePath the path. It is expected to conform to the format {@code
* pathToResource.resourceName}
*/
@Nonnull
public static ResourcePath parse(@Nonnull String qualifiedResourcePath) {
if (qualifiedResourcePath.equals(".")) {
throw new IllegalArgumentException(
"Not a valid resource path: '" + qualifiedResourcePath + "'");
}
int splitPos = qualifiedResourcePath.lastIndexOf(".");
String packagePath;
if (splitPos == -1) {
packagePath = "";
} else {
packagePath = qualifiedResourcePath.substring(0, splitPos);
}
String resourceName = qualifiedResourcePath.substring(splitPos + 1);
return new ResourcePath(packagePath, resourceName);
}
/**
* Generates a new {@code path} given its fully-qualified name.
* <p>
* The name will be interpreted as some location on the file system (that is as {@code
* /path/to/resource}). However, both {@code \} as well as {@code /} will be accepted as path
* separators.
*
* @param qualifiedResourcePath the path. It is expected to conform to the format {@code
* pathToResource/resourceName}
*/
public static ResourcePath parseAsFileSystemPath(@Nonnull String qualifiedResourcePath) {
return parse(toPackageStylePath(qualifiedResourcePath));
}
/**
* Converts a path as used by the file system to the corresponding path as used in a Java package.
*/
protected static String toPackageStylePath(@Nonnull String fileSystemResourcePath) {
return fileSystemResourcePath.replaceAll("[\\\\/]", ".");
}
@Nonnull
protected final String resourcePath;
@Nonnull
protected final String resourceName;
/**
* Full constructor.
*
* @param resourcePath the path to the directory/package in which the resource resides
* @param resourceName the name of the class (or other resource)
*/
public ResourcePath(@Nonnull String resourcePath, @Nonnull String resourceName) {
if (resourceName.isEmpty()) {
throw new IllegalArgumentException("Resource name may not be empty");
}
this.resourcePath = resourcePath;
this.resourceName = resourceName;
}
/**
* Provides the path to the directory/package in which {@code this} resource resides. The path
* will be in "Java-package style", i.e. its components will be separated by {@code .}.
*/
@Nonnull
public String getResourcePath() {
return resourcePath;
}
/**
* Provides the name of the resource which is represented by {@code this} path.
* <p>
* The name is relative to the containing directory/package.
*/
@Nonnull
public String getResourceName() {
return resourceName;
}
/**
* Converts {@code this} path to {@code String}. It will match the format {@code package.name}.
*/
@Nonnull
public String get() {
return this.toString();
}
/**
* Converts {@code this} path to a {@code String}, treating it as some path on the file system.
* <p>
* The components of the path will be separated by the platform-dependent separator for the
* current platform. That is, its result may vary depending on the OS on which it is run.
*/
@Nonnull
public String getAsFileSystemPath() {
return this.toString().replaceAll("\\.", File.pathSeparator);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ResourcePath that = (ResourcePath) o;
return this.resourcePath.equals(that.resourcePath) &&
this.resourceName.equals(that.resourceName);
}
@Override
public int hashCode() {
return Objects.hash(resourcePath, resourceName);
}
@Override
public String toString() {
if (resourcePath.isEmpty()) {
return resourceName;
}
return resourcePath + "." + resourceName;
}
}
package org.rosi_project.model_sync.model_join.representation.core;
import java.util.Objects;
import javax.annotation.Nonnull;
/**
* A {@code TypedAttributePath} extends a default {@link AttributePath} by adding type information,
* i.e. adding information about the type of the object the path references.
* <p>
* As we do not have any knowledge about the actual classes that are represented by paths we may
* not validate, whether the given type indeed matches the attribute's type.
*
* @author Rico Bergmann
*/
public class TypedAttributePath extends AttributePath {
private static final String PATH_TYPE_SEPARATOR = ":";
/**
* Creates a new {@code TypedAttributePath} by combining the provided path and type information.
*/
@Nonnull
public static TypedAttributePath of(@Nonnull AttributePath path, @Nonnull ClassResource type) {
return new TypedAttributePath(path, type);
}
/**
* Generates a new {@code TypedAttributePath} by splitting up the given path into its path and
* type portion. The {@code qualifiedTypedPath} is expected to match the format {@code path:type},
* where {@code path} may be parsed using {@link AttributePath#fromQualifiedPath(String)} and type
* may be parsed using {@link ClassResource#fromQualifiedName(String)}.
*/
@Nonnull
public static TypedAttributePath fromQualifiedTypedPath(@Nonnull String qualifiedTypedPath) {
final int pathTypeSplitPos = qualifiedTypedPath.lastIndexOf(PATH_TYPE_SEPARATOR);
if (pathTypeSplitPos < 0) {
throw new IllegalArgumentException("Missing type portion on '" + qualifiedTypedPath + "'");
}
final String qualifiedPath = qualifiedTypedPath.substring(0, pathTypeSplitPos);
final String type = qualifiedTypedPath.substring(pathTypeSplitPos + 1);
return new TypedAttributePath(AttributePath.fromQualifiedPath(qualifiedPath), ClassResource.fromQualifiedName(type));
}
@Nonnull
private final ClassResource type;
/**
* Full constructor.
*
* @param path the attribute
* @param type the attribute's type
*/
public TypedAttributePath(
@Nonnull AttributePath path,
@Nonnull ClassResource type) {
super(path);
this.type = type;
}
/**
* Provides the type of {@code this} attribute.
*/
@Nonnull
public ClassResource getType() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TypedAttributePath)) {
return false;
}
if (!super.equals(o)) {
return false;
}
TypedAttributePath that = (TypedAttributePath) o;
return type.equals(that.type);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), type);
}
@Override
public String toString() {
return super.toString() + PATH_TYPE_SEPARATOR + type.toString();
}
}
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();
}
package org.rosi_project.model_sync.model_join.representation.grammar;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nonnull;
import org.rosi_project.model_sync.model_join.representation.core.ClassResource;
/**
* A {@code JoinExpression} combines two classes of the source models and
* generates a new view derived from them.
* <p>
* At a conceptual level, these classes are referred to as the {@code left} and
* {@code right} class.
* <p>
* Other than the classes to combine, each join also consists of a number of
* {@link KeepExpression}s which contain the fields which should form the
* resulting class as well as information on how to set their values.
* <p>
* As there are a number of different joins available, this class is left
* {@code abstract} with subclasses to represent the specific joins.
*
* @author Rico Bergmann
* @see NaturalJoinExpression
* @see OuterJoinExpression
* @see ThetaJoinExpression
*/
public abstract class JoinExpression implements Iterable<KeepExpression> {
/**
* The {@code JoinType} defines which rules should be applied when
* performing the join.
*/
public enum JoinType {
/**
* The {@code natural join} combines two classes based on attributes
* with equal name and type.
*
* @see NaturalJoinExpression
*/
NATURAL,
/**
* 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
}
@Nonnull
protected final ClassResource left;
@Nonnull
protected final ClassResource right;
@Nonnull
protected final ClassResource target;
@Nonnull
protected final List<KeepExpression> keeps;
/**
* Full constructor.
*
* @param left
* the left class to use in the join
* @param right
* the right class to use in the join
* @param target
* the name of the resulting view
* @param keeps
* the keep statements in the join
*/
protected JoinExpression(@Nonnull ClassResource left, @Nonnull ClassResource right, @Nonnull ClassResource target,
@Nonnull List<KeepExpression> keeps) {
this.left = left;
this.right = right;
this.target = target;
this.keeps = keeps;
}
/**
* Proof if the right and the left class path are equals.
* @return result value
*/
public boolean isSameElement() {
if (left.getResourceName().equals(right.getResourceName())
&& left.getResourcePath().equals(right.getResourcePath())) {
return true;
}
return false;
}
/**
* Provides the based class used in {@code this} join. Only important for
* outer join in other cases always Left.
*/
@Nonnull
public ClassResource getBaseModel() {
return this.getLeft();
}
/**
* Provides the other class used in {@code this} join. Only important for
* outer join in other cases always Right.
*/
@Nonnull
public ClassResource getOtherModel() {
return this.getRight();
}
/**
* Provides the left class used in {@code this} join.
*/
@Nonnull
public ClassResource getLeft() {
return left;
}
/**
* Provides the right class used in {@code this} join.
*/
@Nonnull
public ClassResource getRight() {
return right;
}
/**
* Provides the name of the class resulting from {@code this} join.
*/
@Nonnull
public ClassResource getTarget() {
return target;
}
/**
* Provides the {@code keep} statements that should be used to build the
* resulting class.
*/
@Nonnull
public Iterable<KeepExpression> getKeeps() {
return keeps;
}
public List<KeepExpression> getKeepsList() {
return Collections.unmodifiableList(keeps);
}
/**
* Provides the type of {@code this} join.
*/
@Nonnull
public abstract JoinType getType();
/**
* Provides the type of {@code this} join as a non-technical {@code String}.
*/
@Nonnull
abstract String getJoinTypeAsString();
@Nonnull
@Override
public Iterator<KeepExpression> iterator() {
return keeps.iterator();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
JoinExpression that = (JoinExpression) o;
return this.target.equals(that.target);
}
@Override
public int hashCode() {
return Objects.hash(target);
}
@Override
public String toString() {
return getJoinTypeAsString() + " " + left.toString() + " with " + right.toString() + " as " + target.toString();
}
}
package org.rosi_project.model_sync.model_join.representation.grammar;
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.RelativeAttribute;
/**
* A {@code KeepAggregateExpression} adds an attribute to some join by calculating an aggregation on
* the referenced collection of numerical values. The possible aggregation operations are equal to
* those provided by the SQL programming language.
* <p>
* The aggregate's result will in turn be some numerical value.
*
* @author Rico Bergmann
*/
public class KeepAggregateExpression extends KeepExpression {
/**
* The {@code AggregationType} defines what aggregation should be performed.
*/
public enum AggregationType {
/**
* The attribute's value should be the sum of all the referenced values.
*/
SUM,
/**
* The attribute's value should be the average value of all the referenced values.
*/
AVG,
/**
* The attribute's value should be the minimum value of all the referenced values.
*/
MIN,
/**
* The attribute's value should be the maximum value of all the referenced values.
*/
MAX,
/**
* The attribute's value should be the number of referenced elements.
*/
SIZE
}
/**
* The {@code AggregateBuilder} enables the construction of new {@link KeepAggregateExpression}s
* through a nice and fluent interface.
*
* @author Rico Bergmann
*/
public static class AggregateBuilder {
@Nonnull
private final AggregationType aggregationType;
@Nonnull
private final RelativeAttribute aggregatedAttribute;
@Nonnull
private AttributePath source;
/**
* Full constructor.
*
* @param aggregationType the aggregation operation that should be performed
* @param aggregatedAttribute the attribute on which the aggregation should be performed
*/
AggregateBuilder(@Nonnull AggregationType aggregationType,
@Nonnull RelativeAttribute aggregatedAttribute) {
this.aggregationType = aggregationType;
this.aggregatedAttribute = aggregatedAttribute;
}
/**
* Specifies the reference destination that provides the attribute to join.
* <p>
* Suppose the following example:
* <pre>
* {@code
* class City {
* List<Street> streets;
* }
*
* class Street {
* int length;
* }
* }
* </pre>
* If one was to calculate the average length of all streets per city, the following aggregation
* could be used: {@code keep aggregate avg(length) over City.streets as avgStreetLength}. In
* this case, {@code City.streets} is the wanted reference destination as it provides the {@code
* length} attribute which should be used for the aggregation.
*/
@Nonnull
public AggregateBuilder over(@Nonnull AttributePath source) {
this.source = source;
return this;
}
/**
* Specifies the name of the aggregation attribute in the target join.
*/
@Nonnull
public KeepAggregateExpression as(@Nonnull AttributePath target) {
return new KeepAggregateExpression(aggregationType, aggregatedAttribute, source, target);
}
}
/**
* Generates a new {@code keep aggregate} expression that sums up the referenced values.
*/
@Nonnull
public static AggregateBuilder sum(@Nonnull RelativeAttribute attribute) {
return new AggregateBuilder(AggregationType.SUM, attribute);
}
/**
* Generates a new {@code keep aggregate} expression that calculates the average value of the
* referenced values.
*/
@Nonnull
public static AggregateBuilder avg(@Nonnull RelativeAttribute attribute) {
return new AggregateBuilder(AggregationType.AVG, attribute);
}
/**
* Generates a new {@code keep aggregate} expression that provides the minimum value of the
* referenced values.
*/
@Nonnull
public static AggregateBuilder min(@Nonnull RelativeAttribute attribute) {
return new AggregateBuilder(AggregationType.MIN, attribute);
}
/**
* Generates a new {@code keep aggregate} expression that provides the maximum value of the
* referenced values.
*/
@Nonnull
public static AggregateBuilder max(@Nonnull RelativeAttribute attribute) {
return new AggregateBuilder(AggregationType.MAX, attribute);
}
/**
* Generates a new {@code keep aggregate} expression that counts the number of values that are
* referenced.
*/
@Nonnull
public static AggregateBuilder size(@Nonnull RelativeAttribute attribute) {
return new AggregateBuilder(AggregationType.SIZE, attribute);
}
@Nonnull
private final AggregationType aggregation;
@Nonnull
private final RelativeAttribute aggregatedAttribute;
@Nonnull
private final AttributePath source;
@Nonnull
private final AttributePath target;
/**
* Full constructor.
*
* @param aggregation the aggregation operation that should be performed
* @param aggregatedAttribute the attribute on which the aggregation should be performed
* @param source the field which provides the {@code aggregatedAttribute}. Therefore this
* attribute has to be multi-valued.
* @param target the name of the attribute under which the aggregation result should be
* available in the join
*/
public KeepAggregateExpression(
@Nonnull AggregationType aggregation,
@Nonnull RelativeAttribute aggregatedAttribute,
@Nonnull AttributePath source,
@Nonnull AttributePath target) {
this.aggregation = aggregation;
this.aggregatedAttribute = aggregatedAttribute;
this.source = source;
this.target = target;
}
/**
* Provides the aggregation operation {@code this} expression will perform.
*/
@Nonnull
public AggregationType getAggregation() {
return aggregation;
}
/**
* Provides the attribute that will be aggregated. As all {@link AggregationType aggregation
* operations} are defined on numerical values, this attribute in turn has to be of numerical
* type.
*/
@Nonnull
public RelativeAttribute getAggregatedAttribute() {
return aggregatedAttribute;
}
/**
* Provides the field which contains the {@link #getAggregatedAttribute() aggregated attribute}.
* As an aggregation will - by definition - be performed upon an arbitrary number of values, this
* attribute has to be some kind of collection.
*/
@Nonnull
public AttributePath getSource() {
return source;
}
/**
* Provides the attribute under which the result of {@code this} aggregation should be made
* available.
*/
@Nonnull
public AttributePath getTarget() {
return target;
}
@Override
public void accept(@Nonnull KeepExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
KeepAggregateExpression that = (KeepAggregateExpression) o;
return aggregation == that.aggregation &&
aggregatedAttribute.equals(that.aggregatedAttribute) &&
source.equals(that.source) &&
target.equals(that.target);
}
@Override
public int hashCode() {
return Objects.hash(aggregation, aggregatedAttribute, source, target);
}
@Override
public String toString() {
String aggregationOperation = aggregation.toString().toLowerCase();
return "keep aggregate " //
+ aggregationOperation + "(" + aggregatedAttribute + ") " //
+ "over" + source //
+ " as " + target;
}
}
package org.rosi_project.model_sync.model_join.representation.grammar;
import com.google.common.collect.Lists;
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;
/**
* A {@code KeepAttributesExpression} simply retains a number of attributes from arbitrary source
* classes and includes them in the target join. The attributes' name and type will be left
* unchanged.
*
* @author Rico Bergmann
*/
public class KeepAttributesExpression extends KeepExpression {
/**
* Generates a new keep expression for a number of attributes.
*/
@Nonnull
public static KeepAttributesExpression keepAttributes(@Nonnull AttributePath firstAttribute,
@Nonnull AttributePath... moreAttributes) {
List<AttributePath> attributes = Lists.newArrayList(moreAttributes);
attributes.add(0, firstAttribute);
return new KeepAttributesExpression(attributes);
}
@Nonnull
private final List<AttributePath> attributes;
/**
* Full constructor.
*
* @param attributes the attributes for which the expression should be created
*/
public KeepAttributesExpression(@Nonnull List<AttributePath> attributes) {
this.attributes = attributes;
}
/**
* Provides the attributes that should be retained in the join.
* <p>
* The returned {@code List} is a shallow copy of the attributes in {@code this} expression.
*/
@Nonnull
public List<AttributePath> getAttributes() {
return new ArrayList<>(attributes);
}
@Override
public void accept(@Nonnull KeepExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
KeepAttributesExpression that = (KeepAttributesExpression) o;
return this.attributes.equals(that.attributes);
}
@Override
public int hashCode() {
return Objects.hash(attributes);
}
@Override
public String toString() {
return "keep attributes " + attributes.stream() //
.map(AttributePath::toString) //
.reduce((firstAttribute, secondAttribute) -> firstAttribute + ", " + secondAttribute) //
.orElseThrow(() -> new IllegalStateException(
"A keep attribute expression has to keep at least one attribute"));
}
}
package org.rosi_project.model_sync.model_join.representation.grammar;
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.OCLStatement;
import org.rosi_project.model_sync.model_join.representation.core.TypedAttributePath;
/**
* A {@code KeepCalculatedExpression} adds an attribute to some join by evaluating an arbitrary
* {@link OCLStatement OCL statement}.
* <p>
* This is the most general kind of {@link KeepExpression} as all other expressions may be expressed
* in OCL as well.
*
* @author Rico Bergmann
* @see OCLStatement
*/
public class KeepCalculatedExpression extends KeepExpression {
/**
* The {@code KeepCalculatedBuilder} enables the construction of new {@link
* KeepCalculatedExpression}s through a nice and fluent interface.
*
* @author Rico Bergmann
*/
public static class KeepCalculatedBuilder {
@Nonnull
private final OCLStatement calculationRule;
/**
* Full constructor.
*
* @param calculationRule the OCL statement that should be used to calculate the attribute's
* value.
*/
KeepCalculatedBuilder(@Nonnull OCLStatement calculationRule) {
this.calculationRule = calculationRule;
}
/**
* Specifies the name of the attribute in the target join.
*/
@Nonnull
public KeepCalculatedExpression as(@Nonnull TypedAttributePath target) {
return new KeepCalculatedExpression(calculationRule, target);
}
}
/**
* Generates a new {@code keep calculated} expression.
*/
@Nonnull
public static KeepCalculatedBuilder keepCalculatedAttribute(
@Nonnull OCLStatement calculationRule) {
return new KeepCalculatedBuilder(calculationRule);
}
@Nonnull
private final OCLStatement calculationRule;
@Nonnull
private final TypedAttributePath target;
/**
* Full constructor.
*
* @param calculationRule the OCL statement that should be used to calculate the attribute's
* value
* @param target the name of the attribute under which the aggregation result should be
* available in the join
*/
public KeepCalculatedExpression(
@Nonnull OCLStatement calculationRule,
@Nonnull TypedAttributePath target) {
this.calculationRule = calculationRule;
this.target = target;
}
/**
* Provides the OCL expression that is used to determine the value of {@code this} attribute.
*/
@Nonnull
public OCLStatement getCalculationRule() {
return calculationRule;
}
/**
* Provides the attribute under which the result of {@code this} calculation should be made
* available.
*/
@Nonnull
public TypedAttributePath getTarget() {
return target;
}
@Override
public void accept(@Nonnull KeepExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
KeepCalculatedExpression that = (KeepCalculatedExpression) o;
return calculationRule.equals(that.calculationRule) &&
target.equals(that.target);
}
@Override
public int hashCode() {
return Objects.hash(calculationRule, target);
}
@Override
public String toString() {
return "keep calculated attribute " + calculationRule + " as " + target;
}
}
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>
* This mostly means retaining some attributes or calculating their value but may also include
* maintaining references to other classes.
*
* @author Rico Bergmann
*/
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);
}
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 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.
* <p>
* 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 CompoundKeepExpression {
/**
* 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;
@Nonnull
private final ReferenceDirection referenceDirection;
@Nonnull
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 ClassResource target,
@Nonnull List<KeepExpression> keeps) {
this.attribute = attribute;
this.referenceDirection = referenceDirection;
this.target = target;
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 ClassResource getTarget() {
return target;
}
/**
* Provides all {@code KeepExpression keep expressions} that should be used to build the Join for
* the referenced instances.
*/
@Nonnull
@Override
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) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
KeepReferenceExpression that = (KeepReferenceExpression) o;
return attribute.equals(that.attribute) && target.equals(that.target);
}
@Override
public int hashCode() {
return Objects.hash(attribute, target);
}
@Override
public String toString() {
String directionString = referenceDirection.toString().toLowerCase();
return "keep " + directionString + " " + attribute + " as type " + target;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment