diff options
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.md | 351 |
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 | --- | ||
2 | SPDX-FileCopyrightText: 2024 The Refinery Authors | ||
3 | SPDX-License-Identifier: EPL-2.0 | ||
4 | sidebar_position: 0 | ||
5 | --- | ||
6 | |||
7 | # Programming guide | ||
8 | |||
9 | This guide is aimed at developers who wish to create Java applications that leverage Refinery as a library. | ||
10 | We also recommend browsing the [Javadoc documentation](../javadoc) associated with Refinery components. | ||
11 | See the [contributor's guide](../contributing) for information on building and modifying Refinery itself. | ||
12 | |||
13 | :::note | ||
14 | |||
15 | Refinery can run as a cloud-based [_Graph Solver as a Service_](https://refinery.services/) without local installation. | ||
16 | You can also run a compiled version as a [Docker container](../../learn/docker). | ||
17 | |||
18 | ::: | ||
19 | |||
20 | Below, 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 | |||
24 | We recommend [Gradle](https://gradle.org/) as a build system for creating Java programs that use Refinery as a library. | ||
25 | We created a [Gradle plugin](pathname://../javadoc/refinery-gradle-plugins/) to simplify project configuration. | ||
26 | |||
27 | 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`. | ||
28 | |||
29 | import TabItem from '@theme/TabItem'; | ||
30 | import 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 | |||
49 | This 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 | |||
56 | See 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 | |||
60 | The 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. | ||
61 | For 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 | |||
80 | The version catalog also contains the external dependencies used by the Refinery framework. | ||
81 | For 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 | |||
104 | You can use the built-in [`application`](https://docs.gradle.org/current/userguide/application_plugin.html) to build stand-alone Java applications. | ||
105 | |||
106 | When 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 | ||
109 | package org.example; | ||
110 | |||
111 | import tools.refinery.generator.standalone.StandaloneRefinery; | ||
112 | |||
113 | import java.io.IOException; | ||
114 | |||
115 | public 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 | |||
143 | 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](https://github.com/Goooler/shadow) plugin. | ||
144 | The 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 | |||
173 | After building your project with `./gradlew build`, you may find the produced "fat JAR" in the `build/libs` directory. | ||
174 | Its file name will be suffixed with `-all.jar`. | ||
175 | In 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 | |||
190 | Be sure to replace `example-0.0.0-SNAPSHOT` with the name and version of your project. | ||
191 | |||
192 | ### Writing tests | ||
193 | |||
194 | Our 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). | ||
195 | It also sets up [Hamcrest](https://hamcrest.org/JavaHamcrest/) for writing assertions. | ||
196 | You should put your test files into the `src/test/java` directory in your projects. | ||
197 | You may run test with the commands `./gradlew test` or `./gradlew build`. | ||
198 | |||
199 | To 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. | ||
200 | Instead, 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 | |||
223 | Afterwards, you can use the `@ExtendWith`, `@InjectWith`, and `@Inject` annotations to set up your unit test. | ||
224 | |||
225 | ```java | ||
226 | package org.example; | ||
227 | |||
228 | import com.google.inject.Inject; | ||
229 | import org.eclipse.xtext.testing.InjectWith; | ||
230 | import org.eclipse.xtext.testing.extensions.InjectionExtension; | ||
231 | import org.junit.jupiter.api.Test; | ||
232 | import org.junit.jupiter.api.extension.ExtendWith; | ||
233 | import tools.refinery.generator.GeneratorResult; | ||
234 | import tools.refinery.generator.ModelGeneratorFactory; | ||
235 | import tools.refinery.generator.ProblemLoader; | ||
236 | import tools.refinery.language.tests.ProblemInjectorProvider; | ||
237 | |||
238 | import java.io.IOException; | ||
239 | |||
240 | import static org.hamcrest.MatcherAssert.assertThat; | ||
241 | import static org.hamcrest.Matchers.is; | ||
242 | |||
243 | // highlight-start | ||
244 | @ExtendWith(InjectionExtension.class) | ||
245 | @InjectWith(ProblemInjectorProvider.class) | ||
246 | // highlight-end | ||
247 | class 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 | |||
280 | 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. | ||
281 | |||
282 | If 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" | ||
285 | tools.refinery.gradle.auto-apply=false | ||
286 | ``` | ||
287 | |||
288 | to the `gradle.properties` file in the root directory of your project. | ||
289 | |||
290 | 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: | ||
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 | |||
309 | 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. | ||
310 | |||
311 | ## Working with Maven {#maven} | ||
312 | |||
313 | You may also develop applications based on Refiney using [Apache Maven](https://maven.apache.org/) as the build system. | ||
314 | Although 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 | |||
316 | 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. | ||
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 | |||
336 | 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: | ||
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 | |||
351 | 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. | ||