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 {