From 68d8bdfc90b1c5dc4f20dda1a8553005e879eaf6 Mon Sep 17 00:00:00 2001
From: Claude
Date: Tue, 9 Jun 2026 05:04:13 +0000
Subject: [PATCH 1/3] Make plugin and OCL extension directories configurable at
startup (#20)
USE resolved the plugin directory and the OCL extensions directory from
fixed locations under the home directory (/lib/plugins and
/oclextensions). This forced every environment (e.g. unit and
integration tests) to mirror that layout.
Add two startup arguments to override these directories independently:
-pluginDir= (default: /lib/plugins)
-extensionsDir= (default: /oclextensions)
Options.processArgs parses the new arguments and only falls back to the
home-relative defaults when they are not given, so existing behavior is
unchanged. The Swing and JavaFX launchers now read the configured OCL
extensions directory (Options.oclExtensionsDir) instead of recomputing
/oclextensions.
Add OptionsDirectoryArgsTest covering the defaults and the independent
overrides.
https://claude.ai/code/session_01A85dAvTAvi2oWkGZH6REEq
---
NEWS | 3 +
.../main/java/org/tzi/use/config/Options.java | 41 +++++-
.../use/config/OptionsDirectoryArgsTest.java | 117 ++++++++++++++++++
.../use/main/gui/fx/JavaFXAppLauncher.java | 2 +-
.../org/tzi/use/main/gui/swing/MainSwing.java | 2 +-
5 files changed, 160 insertions(+), 5 deletions(-)
create mode 100644 use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java
diff --git a/NEWS b/NEWS
index 95c0f3365..33ec62e6f 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,9 @@ Please see the file `README' for a description of how to report bugs.
** Changes between version 7.1.0 and X.X.X
* USE now supports data types
+* [USE] The plugin and OCL extension directories can now be configured at
+ startup with the -pluginDir and -extensionsDir arguments, instead of being
+ fixed to /lib/plugins and /oclextensions (#20)
** Changes between version 5.2.0 and 6.0.0
* New OCL complexity plugin
diff --git a/use-core/src/main/java/org/tzi/use/config/Options.java b/use-core/src/main/java/org/tzi/use/config/Options.java
index 9064cdb5e..b5f0b484c 100644
--- a/use-core/src/main/java/org/tzi/use/config/Options.java
+++ b/use-core/src/main/java/org/tzi/use/config/Options.java
@@ -184,6 +184,11 @@ public static WarningType getType(String type) {
*/
public static Path pluginDir = null;
+ /**
+ * Directory containing the OCL extension definitions.
+ */
+ public static Path oclExtensionsDir = null;
+
/**
* Preferred size of the diagram views.
*/
@@ -246,6 +251,12 @@ private static void printHelp() {
System.out.println(" -noplugins do not use plugins");
System.out.println(" -h print help");
System.out.println(" -H=path home of use installation");
+ System.out.println(" -pluginDir=path");
+ System.out.println(" directory to load plugins from");
+ System.out.println(" (default: /lib/plugins)");
+ System.out.println(" -extensionsDir=path");
+ System.out.println(" directory to load OCL extensions from");
+ System.out.println(" (default: /oclextensions)");
System.out.println(" -nr suppress warnings about missing readline library");
System.out.println(" -q reads spec_file, executes cmd_file, and checks constraints");
System.out.println(" exit code is 1 if constraints fail, otherwise 0");
@@ -319,6 +330,7 @@ public static void resetOptions() {
checkWarningsUnrelatedTypes = WarningType.WARN;
doPLUGIN = true;
pluginDir = null;
+ oclExtensionsDir = null;
fDiagramDimension = new Dimension( 600, 600 );
props = null;
specFilename = null;
@@ -349,14 +361,30 @@ public static void processArgs(String[] args) {
Options.doGUI = false;
} else if (arg.equals("noplugins")) {
Options.doPLUGIN = false;
- } else if (arg.startsWith("H=")) {
+ } else if (arg.startsWith("H=")) {
try {
homeDir = Paths.get(arg.substring(2));
} catch (InvalidPathException e) {
System.err.println("Invalid path " + StringUtil.inQuotes(arg.substring(2)) + " for home directory specified.");
System.exit(1);
}
- } else if (arg.equals("nr")) {
+ } else if (arg.startsWith("pluginDir=")) {
+ String value = arg.substring("pluginDir=".length());
+ try {
+ pluginDir = Paths.get(value);
+ } catch (InvalidPathException e) {
+ System.err.println("Invalid path " + StringUtil.inQuotes(value) + " for plugin directory specified.");
+ System.exit(1);
+ }
+ } else if (arg.startsWith("extensionsDir=")) {
+ String value = arg.substring("extensionsDir=".length());
+ try {
+ oclExtensionsDir = Paths.get(value);
+ } catch (InvalidPathException e) {
+ System.err.println("Invalid path " + StringUtil.inQuotes(value) + " for OCL extensions directory specified.");
+ System.exit(1);
+ }
+ } else if (arg.equals("nr")) {
suppressWarningsAboutMissingReadlineLibrary = true;
} else if (arg.equals("q")) {
Options.quiet = true;
@@ -441,7 +469,14 @@ public static void processArgs(String[] args) {
}
setLastDirectory(homeDir);
- pluginDir = homeDir.resolve("lib").resolve("plugins");
+ // Both directories default to a location below the home directory, but
+ // can be overridden independently via -pluginDir / -extensionsDir.
+ if (pluginDir == null) {
+ pluginDir = homeDir.resolve("lib").resolve("plugins");
+ }
+ if (oclExtensionsDir == null) {
+ oclExtensionsDir = homeDir.resolve("oclextensions");
+ }
if (quiet && (specFilename == null || cmdFilename == null) ) {
System.err
diff --git a/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java b/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java
new file mode 100644
index 000000000..e0c7918f3
--- /dev/null
+++ b/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java
@@ -0,0 +1,117 @@
+/*
+ * USE - UML based specification environment
+ * Copyright (C) 1999-2004 Mark Richters, University of Bremen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package org.tzi.use.config;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Properties;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for the configurable directories for plugins and OCL extensions
+ * (issue #20).
+ *
+ * By default both directories are derived from the USE home directory
+ * ({@code -H}): plugins live in {@code /lib/plugins} and OCL extensions
+ * in {@code /oclextensions}. These tests verify that both defaults are
+ * applied and that each directory can be overridden independently with a
+ * startup argument ({@code -pluginDir=} and {@code -extensionsDir=}).
+ *
+ * @author Claude
+ */
+public class OptionsDirectoryArgsTest {
+
+ private Properties savedSystemProperties;
+
+ @BeforeEach
+ public void setUp() {
+ // processArgs() augments the global system properties; remember them
+ // so we can restore them and keep the test isolated.
+ savedSystemProperties = (Properties) System.getProperties().clone();
+ Options.resetOptions();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ Options.resetOptions();
+ System.setProperties(savedSystemProperties);
+ }
+
+ private Path home() {
+ return Paths.get(System.getProperty("java.io.tmpdir"), "use-home-under-test");
+ }
+
+ @Test
+ public void pluginDirDefaultsToHomeLibPlugins() {
+ Path home = home();
+ Options.processArgs(new String[] { "-H=" + home, "-c" });
+ assertEquals(home.resolve("lib").resolve("plugins"), Options.pluginDir,
+ "plugin directory must default to /lib/plugins");
+ }
+
+ @Test
+ public void extensionsDirDefaultsToHomeOclextensions() {
+ Path home = home();
+ Options.processArgs(new String[] { "-H=" + home, "-c" });
+ assertEquals(home.resolve("oclextensions"), Options.oclExtensionsDir,
+ "OCL extensions directory must default to /oclextensions");
+ }
+
+ @Test
+ public void pluginDirCanBeOverridden() {
+ Path home = home();
+ Path custom = Paths.get(System.getProperty("java.io.tmpdir"), "custom-plugins");
+ Options.processArgs(new String[] { "-H=" + home, "-pluginDir=" + custom, "-c" });
+ assertEquals(custom, Options.pluginDir,
+ "-pluginDir must override the plugin directory");
+ assertEquals(home.resolve("oclextensions"), Options.oclExtensionsDir,
+ "overriding the plugin directory must not affect the extensions directory");
+ }
+
+ @Test
+ public void extensionsDirCanBeOverridden() {
+ Path home = home();
+ Path custom = Paths.get(System.getProperty("java.io.tmpdir"), "custom-extensions");
+ Options.processArgs(new String[] { "-H=" + home, "-extensionsDir=" + custom, "-c" });
+ assertEquals(custom, Options.oclExtensionsDir,
+ "-extensionsDir must override the OCL extensions directory");
+ assertEquals(home.resolve("lib").resolve("plugins"), Options.pluginDir,
+ "overriding the extensions directory must not affect the plugin directory");
+ }
+
+ @Test
+ public void bothDirectoriesCanBeOverriddenIndependentlyOfHome() {
+ Path home = home();
+ Path customPlugins = Paths.get(System.getProperty("java.io.tmpdir"), "p");
+ Path customExtensions = Paths.get(System.getProperty("java.io.tmpdir"), "e");
+ Options.processArgs(new String[] {
+ "-H=" + home,
+ "-pluginDir=" + customPlugins,
+ "-extensionsDir=" + customExtensions,
+ "-c" });
+ assertEquals(customPlugins, Options.pluginDir);
+ assertEquals(customExtensions, Options.oclExtensionsDir);
+ }
+}
diff --git a/use-gui/src/main/java/org/tzi/use/main/gui/fx/JavaFXAppLauncher.java b/use-gui/src/main/java/org/tzi/use/main/gui/fx/JavaFXAppLauncher.java
index 0de0b4026..eda4516ca 100644
--- a/use-gui/src/main/java/org/tzi/use/main/gui/fx/JavaFXAppLauncher.java
+++ b/use-gui/src/main/java/org/tzi/use/main/gui/fx/JavaFXAppLauncher.java
@@ -61,7 +61,7 @@ public void start(Stage primaryStage) throws Exception {
private void initExtensions() {
if (!Options.disableExtensions) {
- ExtensionManager.EXTENSIONS_FOLDER = Options.homeDir + Options.FILE_SEPARATOR + "oclextensions";
+ ExtensionManager.EXTENSIONS_FOLDER = Options.oclExtensionsDir.toString();
ExtensionManager.getInstance().loadExtensions();
}
}
diff --git a/use-gui/src/main/java/org/tzi/use/main/gui/swing/MainSwing.java b/use-gui/src/main/java/org/tzi/use/main/gui/swing/MainSwing.java
index 0723eaa45..f535da01a 100644
--- a/use-gui/src/main/java/org/tzi/use/main/gui/swing/MainSwing.java
+++ b/use-gui/src/main/java/org/tzi/use/main/gui/swing/MainSwing.java
@@ -42,7 +42,7 @@ public void launch(String[] args) {
MSystem system = null;
if (!Options.disableExtensions) {
- ExtensionManager.EXTENSIONS_FOLDER = Options.homeDir + Options.FILE_SEPARATOR + "oclextensions";
+ ExtensionManager.EXTENSIONS_FOLDER = Options.oclExtensionsDir.toString();
ExtensionManager.getInstance().loadExtensions();
}
From 0960cac1c636d85586880b2184572f7e011ba7a7 Mon Sep 17 00:00:00 2001
From: Claude
Date: Tue, 9 Jun 2026 06:32:16 +0000
Subject: [PATCH 2/3] Use the project's current copyright notice on new files;
drop @author tag
---
.../test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java b/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java
index e0c7918f3..4bdc43916 100644
--- a/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java
+++ b/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java
@@ -1,6 +1,6 @@
/*
* USE - UML based specification environment
- * Copyright (C) 1999-2004 Mark Richters, University of Bremen
+ * Copyright (C) 1999-2025 University of Bremen & University of Applied Sciences Hamburg
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -39,7 +39,6 @@
* applied and that each directory can be overridden independently with a
* startup argument ({@code -pluginDir=} and {@code -extensionsDir=}).
*
- * @author Claude
*/
public class OptionsDirectoryArgsTest {
From 4d79f413b4dd9049db5106ec492fd0d495f09116 Mon Sep 17 00:00:00 2001
From: Claude
Date: Tue, 9 Jun 2026 06:40:14 +0000
Subject: [PATCH 3/3] Add @author tags and bump copyright year to 2026 on new
files
Add @author Cansin Yildiz and @author Claude to the files added in this
branch, and update the copyright year to 1999-2026.
Co-authored-by: Claude
https://claude.ai/code/session_01A85dAvTAvi2oWkGZH6REEq
---
.../java/org/tzi/use/config/OptionsDirectoryArgsTest.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java b/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java
index 4bdc43916..b5ac5a94d 100644
--- a/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java
+++ b/use-core/src/test/java/org/tzi/use/config/OptionsDirectoryArgsTest.java
@@ -1,6 +1,6 @@
/*
* USE - UML based specification environment
- * Copyright (C) 1999-2025 University of Bremen & University of Applied Sciences Hamburg
+ * Copyright (C) 1999-2026 University of Bremen & University of Applied Sciences Hamburg
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -39,6 +39,8 @@
* applied and that each directory can be overridden independently with a
* startup argument ({@code -pluginDir=} and {@code -extensionsDir=}).
*
+ * @author Cansin Yildiz
+ * @author Claude
*/
public class OptionsDirectoryArgsTest {