Skip to main content
Version: 0.1.3

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.

note

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.

settings.gradle.kts
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:

build.gradle.kts
depndencies {
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:

build.gradle.kts
depndencies {
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:

build.gradle.kts
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

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.

build.gradle.kts
depndencies {
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

gradle.properties
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:

build.gradle.kts
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.

pom.xml
<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:

pom.xml
<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.