Programming guide
This guide is aimed at developers who wish to create Java applications that leverage Refinery as a library. We also recommend browsing the Javadoc documentation associated with Refinery components. See the contributor’s guide for information on building and modifying Refinery itself.
Refinery can run as a cloud-based Graph Solver as a Service without local installation. You can also run a compiled version as a Docker container.
Below, you can find instructions on using Gradle or Apache Maven to create applications that use Refinery as a Java library.
Working with Gradle​
We recommend Gradle as a build system for creating Java programs that use Refinery as a library. We created a Gradle plugin to simplify project configuration.
To find out the configuration for using our artifacts, select whether you use a Kotlin-based (.gradle.kts
) or a Groovy-based (.gradle
) configuration format for your Gradle build. You should add this code to your Gradle settings file, which is named settings.gradle.kts
or settings.gradle
.
- Kotlin
- Groovy
plugins {
id("tools.refinery.settings") version "0.1.3"
}
plugins {
id 'tools.refinery.settings' version '0.1.3'
}
This plugin will perform the following actions automatically:
- Add a version catalog to your build to enable easy access to Refinery artifacts and their dependencies.
- Lock refinery artifacts and their dependencies to a platform (Maven BOM) of tested versions.
- Configure a logger based on Log4J over SLF4J and SLF4J Simple that is free from vulnerabilities and works out of the box for most use-cases.
- Generate application artifacts, if any, according to best practices used in the Refinery project.
- Add common dependencies for writing unit tests for your Java code.
See the multi-module projects section of this tutorial on how to disable some of these automated actions for a part of your build.
Declaring dependencies​
The Refinery Gradle plugins adds a version catalog named refinery
that you can use to quickly access dependencies.
For example, to add a dependency to the tools.refinery:refinery-generator
library, you add the following to your build.gradle.kts
or build.gradle
file:
- Kotlin
- Groovy
depndencies {
implementation(refinery.generator)
}
dependencies {
implementation refinery.generator
}
The version catalog also contains the external dependencies used by the Refinery framework. For example, you may add GSON for JSON parsing and JCommander for command-line argument parsing as follows:
- Kotlin
- Groovy
depndencies {
implementation(refinery.gson)
implementation(refinery.jcommander)
}
dependencies {
implementation refinery.gson
implementation refinery.jcommander
}
Building applications​
You can use the built-in application
to build stand-alone Java applications.
When developing you main application code in the src/main/java
directory of you project, you can use the StandaloneRefinery
class from tools.refinery:refinery-generator
to access Refinery generator components. See the tutorial on Xtext’s dependency injection for more advanced use-cases.
package org.example;
import tools.refinery.generator.standalone.StandaloneRefinery;
import java.io.IOException;
public class ExampleMain {
public static void main(String[] args) throws IOException {
var problem = StandaloneRefinery.getProblemLoader().loadString("""
class Filesystem {
contains Directory[1] root
}
class File.
class Directory extends File {
contains Directory[] children
}
scope Filesystem = 1, File = 20.
""");
var generator = StandaloneRefinery.getGeneratorFactory().createGenerator(problem);
generator.generate();
var trace = generator.getProblemTrace();
var childrenRelation = trace.getPartialRelation("Directory::children");
var childrenInterpretation = generator.getPartialInterpretation(childrenRelation);
var cursor = childrenInterpretation.getAll();
while (cursor.move()) {
System.out.printf("%s: %s%n", cursor.getKey(), cursor.getValue());
}
}
}
If you want to produce a “fat JAR” that embeds all dependencies (e.g., for invoking from the command line or from Python with a single command), you should also add the shadow plugin. The recommended version of the shadow plugin is set in our version catalog. You can add it to your build script as follows:
- Kotlin
- Groovy
plugins {
application
alias(refinery.plugins.shadow)
}
application {
mainClass = "org.example.ExampleMain"
}
plugins {
application
alias refinery.plugins.shadow
}
application {
mainClass 'org.example.ExampleMain'
}
After building your project with ./gradlew build
, you may find the produced “fat JAR” in the build/libs
directory.
Its file name will be suffixed with -all.jar
.
In you have Java 21 installed, you’ll be able to run the application with the command
- Linux or macOS
- Windows (PowerShell)
java -jar ./build/libs/example-0.0.0-SNAPSHOT-all.jar
java -jar .\build\libs\example-0.0.0-SNAPSHOT-all.jar
Be sure to replace example-0.0.0-SNAPSHOT
with the name and version of your project.
Writing tests​
Our Gradle plugin automatically sets up JUnit 5 for writing tests and parameterized tests.
It also sets up Hamcrest for writing assertions.
You should put your test files into the src/test/java
directory in your projects.
You may run test with the commands ./gradlew test
or ./gradlew build
.
To ensure that your tests are properly isolated, you should not rely on the StandaloneRefinery
class from tools.refinery:refinery-generator
when accessing Refinery generator components.
Instead, you should use Xtext’s dependency injection and unit testing support to instantiate the components. You’ll need to add a dependency to Refinery’s Xtext testing support library to your project.
- Kotlin
- Groovy
depndencies {
implementation(refinery.generator)
testImplementation(testFixtures(refinery.language))
}
dependencies {
implementation refinery.generator
testImplementation testFixtures(refinery.language)
}
The test fixtures for refinery-language
include the @InjectWithRefinery
composed annotation to simplify Xtext injector configuration.
You can use this annotation in conjunction with @Inject
annotations to set up your unit test.
package org.example;
import com.google.inject.Inject;
import org.junit.jupiter.api.Test;
import tools.refinery.generator.GeneratorResult;
import tools.refinery.generator.ModelGeneratorFactory;
import tools.refinery.generator.ProblemLoader;
import tools.refinery.language.tests.InjectWithRefinery;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
@InjectWithRefinery
class ExampleTest {
@Inject
private ProblemLoader problemLoader;
@Inject
private ModelGeneratorFactory generatorFactory;
@Test
void testModelGeneration() throws IOException {
var problem = problemLoader.loadString("""
class Filesystem {
contains Directory[1] root
}
class File.
class Directory extends File {
contains Directory[] children
}
scope Filesystem = 1, File = 20.
""");
var generator = generatorFactory.createGenerator(problem);
var result = generator.tryGenerate();
assertThat(result, is(GeneratorResult.SUCCESS));
}
}
Multi-module projects​
By default, the tools.refinery.settings
plugin will apply our tools.refinery.java
plugin to all Java projects in your build and configure them for use with Refinery. This is sufficient for single-module Java projects, and multi-module projects where all of your Java modules use Refinery.
If you wish to use Refinery in only some modules in your multi-module project, you can disable this behavior by adding
tools.refinery.gradle.auto-apply=false
to the gradle.properties
file in the root directory of your project.
If you use this setting, you’ll need to add the tools.refinery.java
plugin manually to any Java projects where you want to use Refinery like this:
- Kotlin
- Groovy
plugins {
id("tools.refinery.java")
}
plugins {
id 'tools.refinery.java'
}
Do not attempt to set a version
for this plugin, because versioning is already managed by the tools.refinery.settings
plugin. Trying to set a version for the tools.refinery.java
plugin separately will result in a Gradle error.
Working with Maven​
You may also develop applications based on Refinery using Apache Maven as the build system. Although we don’t provide a Maven plugin for simplified configuration, you can still use our platform (Maven BOM) to lock the versions of Refinery and its dependencies to tested versions.
You should add the following configuration to your pom.xml
file. If you use multi-module projects, we recommend that you add this to your parent POM.
<project>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>tools.refinery</groupId>
<artifactId>refinery-bom</artifactId>
<version>0.1.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
...
</project>
You’ll be able to add dependencies to Refinery components without an explicit reference to the dependency version, since version numbers are managed by the BOM:
<project>
...
<dependencies>
<dependency>
<groupId>tools.refinery</groupId>
<artifactId>refinery-generator</artifactId>
</dependency>
</dependencies>
...
</project>
However, since the Maven BOM doesn’t offer additional configuration, you’ll have to take care of tasks such as configuring logging and testing, as well as building applications yourself.