aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/docs/versioned_docs/version-0.1.0/develop/java.md
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/docs/versioned_docs/version-0.1.0/develop/java.md')
-rw-r--r--subprojects/docs/versioned_docs/version-0.1.0/develop/java.md351
1 files changed, 351 insertions, 0 deletions
diff --git a/subprojects/docs/versioned_docs/version-0.1.0/develop/java.md b/subprojects/docs/versioned_docs/version-0.1.0/develop/java.md
new file mode 100644
index 00000000..26ba89f5
--- /dev/null
+++ b/subprojects/docs/versioned_docs/version-0.1.0/develop/java.md
@@ -0,0 +1,351 @@
1---
2SPDX-FileCopyrightText: 2024 The Refinery Authors
3SPDX-License-Identifier: EPL-2.0
4sidebar_position: 0
5---
6
7# Programming guide
8
9This guide is aimed at developers who wish to create Java applications that leverage Refinery as a library.
10We also recommend browsing the [Javadoc documentation](../javadoc) associated with Refinery components.
11See the [contributor's guide](../contributing) for information on building and modifying Refinery itself.
12
13:::note
14
15Refinery can run as a cloud-based [_Graph Solver as a Service_](https://refinery.services/) without local installation.
16You can also run a compiled version as a [Docker container](../../learn/docker).
17
18:::
19
20Below, you can find instructions on using [Gradle](#gradle) or [Apache Maven](#maven) to create applications that use Refinery as a Java library.
21
22## Working with Gradle {#gradle}
23
24We recommend [Gradle](https://gradle.org/) as a build system for creating Java programs that use Refinery as a library.
25We created a [Gradle plugin](pathname://../javadoc/refinery-gradle-plugins/) to simplify project configuration.
26
27To 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`.
28
29import TabItem from '@theme/TabItem';
30import Tabs from '@theme/Tabs';
31
32<Tabs groupId="gradleLanguage">
33 <TabItem value="kotlin" label="Kotlin">
34 ```kotlin title="settings.gradle.kts"
35 plugins {
36 id("tools.refinery.settings") version "0.1.0"
37 }
38 ```
39 </TabItem>
40 <TabItem value="groovy" label="Groovy">
41 ```groovy title="settings.gradle"
42 plugins {
43 id 'tools.refinery.settings' version '0.1.0'
44 }
45 ```
46 </TabItem>
47</Tabs>
48
49This plugin will perform the following actions automatically:
50* Add a [version catalog](https://docs.gradle.org/current/userguide/platforms.html#sec:sharing-catalogs) to your build to enable easy access to Refinery artifacts and their [dependencies](#declaring-dependencies).
51* Lock refinery artifacts and their dependencies to a [platform](https://docs.gradle.org/current/userguide/platforms.html#sub:using-platform-to-control-transitive-deps) (Maven BOM) of tested versions.
52* Configure a logger based on [Log4J over SLF4J](https://www.slf4j.org/legacy.html) and [SLF4J Simple](https://www.slf4j.org/apidocs/org/slf4j/simple/SimpleLogger.html) that is free from vulnerabilities and works out of the box for most use-cases.
53* Generate [application](#building-applications) artifacts, if any, according to best practices used in the Refinery project.
54* Add common dependencies for writing [unit tests](#writing-tests) for your Java code.
55
56See the [multi-module projects](#multi-module-projects) section of this tutorial on how to disable some of these automated actions for a part of your build.
57
58### Declaring dependencies
59
60The Refinery Gradle plugins adds a [version catalog](https://docs.gradle.org/current/userguide/platforms.html#sec:sharing-catalogs) named `refinery` that you can use to quickly access dependencies.
61For example, to add a dependency to the [`tools.refinery:refinery-generator`](pathname://../javadoc/refinery-generator/) library, you add the following to your `build.gradle.kts` or `build.gradle` file:
62
63<Tabs groupId="gradleLanguage">
64 <TabItem value="kotlin" label="Kotlin">
65 ```kotlin title="build.gradle.kts"
66 depndencies {
67 implementation(refinery.generator)
68 }
69 ```
70 </TabItem>
71 <TabItem value="groovy" label="Groovy">
72 ```groovy title="build.gradle"
73 dependencies {
74 implementation refinery.generator
75 }
76 ```
77 </TabItem>
78</Tabs>
79
80The version catalog also contains the external dependencies used by the Refinery framework.
81For example, you may add [GSON](https://google.github.io/gson/) for JSON parsing and [JCommander](https://jcommander.org/) for command-line argument parsing as follows:
82
83<Tabs groupId="gradleLanguage">
84 <TabItem value="kotlin" label="Kotlin">
85 ```kotlin title="build.gradle.kts"
86 depndencies {
87 implementation(refinery.gson)
88 implementation(refinery.jcommander)
89 }
90 ```
91 </TabItem>
92 <TabItem value="groovy" label="Groovy">
93 ```groovy title="build.gradle"
94 dependencies {
95 implementation refinery.gson
96 implementation refinery.jcommander
97 }
98 ```
99 </TabItem>
100</Tabs>
101
102### Building applications
103
104You can use the built-in [`application`](https://docs.gradle.org/current/userguide/application_plugin.html) to build stand-alone Java applications.
105
106When developing you main application code in the `src/main/java` directory of you project, you can use the [`StandaloneRefinery`](pathname://../javadoc/refinery-generator/tools/refinery/generator/standalone/StandaloneRefinery.html) class from [`tools.refinery:refinery-generator`](pathname://../javadoc/refinery-generator/) to access Refinery generator components. See the tutorial on Xtext's [dependency injection](https://eclipse.dev/Xtext/documentation/302_configuration.html#dependency-injection) for more advanced use-cases.
107
108```java
109package org.example;
110
111import tools.refinery.generator.standalone.StandaloneRefinery;
112
113import java.io.IOException;
114
115public class ExampleMain {
116 public static void main(String[] args) throws IOException {
117 var problem = StandaloneRefinery.getProblemLoader().loadString("""
118 class Filesystem {
119 contains Directory[1] root
120 }
121
122 class File.
123
124 class Directory extends File {
125 contains Directory[] children
126 }
127
128 scope Filesystem = 1, File = 20.
129 """);
130 var generator = StandaloneRefinery.getGeneratorFactory().createGenerator(problem);
131 generator.generate();
132 var trace = generator.getProblemTrace();
133 var childrenRelation = trace.getPartialRelation("Directory::children");
134 var childrenInterpretation = generator.getPartialInterpretation(childrenRelation);
135 var cursor = childrenInterpretation.getAll();
136 while (cursor.move()) {
137 System.out.printf("%s: %s%n", cursor.getKey(), cursor.getValue());
138 }
139 }
140}
141```
142
143If 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](https://github.com/Goooler/shadow) plugin.
144The recommended version of the shadow plugin is set in our [version catalog](#declaring-dependencies). You can add it to your build script as follows:
145
146<Tabs groupId="gradleLanguage">
147 <TabItem value="kotlin" label="Kotlin">
148 ```kotlin title="build.gradle.kts"
149 plugins {
150 application
151 alias(refinery.plugins.shadow)
152 }
153
154 application {
155 mainClass = "org.example.ExampleMain"
156 }
157 ```
158 </TabItem>
159 <TabItem value="groovy" label="Groovy">
160 ```groovy title="build.gradle"
161 plugins {
162 application
163 alias refinery.plugins.shadow
164 }
165
166 application {
167 mainClass 'org.example.ExampleMain'
168 }
169 ```
170 </TabItem>
171</Tabs>
172
173After building your project with `./gradlew build`, you may find the produced "fat JAR" in the `build/libs` directory.
174Its file name will be suffixed with `-all.jar`.
175In you have Java 21 installed, you'll be able to run the application with the command
176
177<Tabs groupId="posix2windows">
178 <TabItem value="posix" label="Linux or macOS">
179 ```bash
180 java -jar ./build/libs/example-0.0.0-SNAPSHOT-all.jar
181 ```
182 </TabItem>
183 <TabItem value="windows" label="Windows (PowerShell)">
184 ```bash
185 java -jar .\build\libs\example-0.0.0-SNAPSHOT-all.jar
186 ```
187 </TabItem>
188</Tabs>
189
190Be sure to replace `example-0.0.0-SNAPSHOT` with the name and version of your project.
191
192### Writing tests
193
194Our Gradle plugin automatically sets up [JUnit 5](https://junit.org/junit5/) for writing tests and [parameterized tests](https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests).
195It also sets up [Hamcrest](https://hamcrest.org/JavaHamcrest/) for writing assertions.
196You should put your test files into the `src/test/java` directory in your projects.
197You may run test with the commands `./gradlew test` or `./gradlew build`.
198
199To ensure that your tests are properly isolated, you should *not* rely on the [`StandaloneRefinery`](pathname://../javadoc/refinery-generator/tools/refinery/generator/standalone/StandaloneRefinery.html) class from [`tools.refinery:refinery-generator`](pathname://../javadoc/refinery-generator/) when accessing Refinery generator components.
200Instead, you should use Xtext's [dependency injection](https://eclipse.dev/Xtext/documentation/302_configuration.html#dependency-injection) and [unit testing](https://eclipse.dev/Xtext/documentation/103_domainmodelnextsteps.html#tutorial-unit-tests) support to instantiate the components. You'll need to add a dependency to Refinery's Xtext testing support library to your project.
201
202<Tabs groupId="gradleLanguage">
203 <TabItem value="kotlin" label="Kotlin">
204 ```kotlin title="build.gradle.kts"
205 depndencies {
206 implementation(refinery.generator)
207 // highlight-next-line
208 testImplementation(testFixtures(refinery.language))
209 }
210 ```
211 </TabItem>
212 <TabItem value="groovy" label="Groovy">
213 ```groovy title="build.gradle"
214 dependencies {
215 implementation refinery.generator
216 // highlight-next-line
217 testImplementation testFixtures(refinery.language)
218 }
219 ```
220 </TabItem>
221</Tabs>
222
223Afterwards, you can use the `@ExtendWith`, `@InjectWith`, and `@Inject` annotations to set up your unit test.
224
225```java
226package org.example;
227
228import com.google.inject.Inject;
229import org.eclipse.xtext.testing.InjectWith;
230import org.eclipse.xtext.testing.extensions.InjectionExtension;
231import org.junit.jupiter.api.Test;
232import org.junit.jupiter.api.extension.ExtendWith;
233import tools.refinery.generator.GeneratorResult;
234import tools.refinery.generator.ModelGeneratorFactory;
235import tools.refinery.generator.ProblemLoader;
236import tools.refinery.language.tests.ProblemInjectorProvider;
237
238import java.io.IOException;
239
240import static org.hamcrest.MatcherAssert.assertThat;
241import static org.hamcrest.Matchers.is;
242
243// highlight-start
244@ExtendWith(InjectionExtension.class)
245@InjectWith(ProblemInjectorProvider.class)
246// highlight-end
247class ExampleTest {
248 // highlight-start
249 @Inject
250 private ProblemLoader problemLoader;
251
252 @Inject
253 private ModelGeneratorFactory generatorFactory;
254 // highlight-end
255
256 @Test
257 void testModelGeneration() throws IOException {
258 var problem = problemLoader.loadString("""
259 class Filesystem {
260 contains Directory[1] root
261 }
262
263 class File.
264
265 class Directory extends File {
266 contains Directory[] children
267 }
268
269 scope Filesystem = 1, File = 20.
270 """);
271 var generator = generatorFactory.createGenerator(problem);
272 var result = generator.tryGenerate();
273 assertThat(result, is(GeneratorResult.SUCCESS));
274 }
275}
276```
277
278### Multi-module projects
279
280By 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.
281
282If you wish to use Refinery in only some modules in your multi-module project, you can disable this behavior by adding
283
284```ini title="gradle.properties"
285tools.refinery.gradle.auto-apply=false
286```
287
288to the `gradle.properties` file in the root directory of your project.
289
290If 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:
291
292<Tabs groupId="gradleLanguage">
293 <TabItem value="kotlin" label="Kotlin">
294 ```kotlin title="build.gradle.kts"
295 plugins {
296 id("tools.refinery.java")
297 }
298 ```
299 </TabItem>
300 <TabItem value="groovy" label="Groovy">
301 ```groovy title="build.gradle"
302 plugins {
303 id 'tools.refinery.java'
304 }
305 ```
306 </TabItem>
307</Tabs>
308
309Do *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.
310
311## Working with Maven {#maven}
312
313You may also develop applications based on Refiney using [Apache Maven](https://maven.apache.org/) as the build system.
314Although we don't provide a Maven plugin for simplified configuration, you can still use our [platform](https://docs.gradle.org/current/userguide/platforms.html#sub:using-platform-to-control-transitive-deps) (Maven BOM) to lock the versions of Refinery and its dependencies to tested versions.
315
316You 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.
317
318```xml title="pom.xml"
319<project>
320 ...
321 <dependencyManagement>
322 <dependencies>
323 <dependency>
324 <groupId>tools.refinery</groupId>
325 <artifactId>refinery-bom</artifactId>
326 <version>0.1.0</version>
327 <type>pom</type>
328 <scope>import</scope>
329 </dependency>
330 </dependencies>
331 </dependencyManagement>
332 ...
333</project>
334```
335
336You'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:
337
338```xml title="pom.xml"
339<project>
340 ...
341 <dependencies>
342 <dependency>
343 <groupId>tools.refinery</groupId>
344 <artifactId>refinery-generator</artifactId>
345 </dependency>
346 </dependencies>
347 ...
348</project>
349```
350
351However, 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.