diff --git a/controller/src/main/java/org/jboss/as/controller/ModuleIdentifierUtil.java b/controller/src/main/java/org/jboss/as/controller/ModuleIdentifierUtil.java index 9ae22bcd0ce..b9cc81f6c62 100644 --- a/controller/src/main/java/org/jboss/as/controller/ModuleIdentifierUtil.java +++ b/controller/src/main/java/org/jboss/as/controller/ModuleIdentifierUtil.java @@ -4,6 +4,8 @@ */ package org.jboss.as.controller; +import java.util.function.BiFunction; + import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; @@ -21,32 +23,88 @@ public final class ModuleIdentifierUtil { * @return the canonical representation. Will not return @{code null} */ public static String canonicalModuleIdentifier(String moduleSpec) { + return parseModuleIdentifier(moduleSpec, ModuleIdentifierUtil::canonicalModuleIdentifier); + } + + /** + * Parses the given module identifier into name and optional slot elements, passing those to the given + * function and returning the result of that function. + *

+ * This variant does not {@link #canonicalModuleIdentifier(String) canonicalize} the given identifier. + * + * @param moduleIdentifier an identifier for a module. Cannot be {@code null} + * @param function a function to apply to the module's name and optional slot. Cannot be {@code null}. + * The slot value passed to the function may be null if the identifier does not contain one. + * @return the value returned by {@code function} + * @param the type returned by {@code function} + */ + public static R parseModuleIdentifier(String moduleIdentifier, BiFunction function) { + return parseModuleIdentifier(moduleIdentifier, function, false, null); + } + + + /** + * Parses the given module identifier into name and optional slot elements, passing those to the given + * function and returning the result of that function. + *

+ * + * @param moduleIdentifier an identifier for a module. Cannot be {@code null} + * @param function a function to apply to the module's name and optional slot. Cannot be {@code null}. + * The slot value passed to the function may be null if the identifier does not contain one. + * @param canonicalize if {@code true} the identifier will be {@link #canonicalModuleIdentifier(String) canonicalized} before parsing + * @return the value returned by {@code function} + * @param the type returned by {@code function} + */ + public static R parseModuleIdentifier(String moduleIdentifier, BiFunction function, boolean canonicalize) { + return parseModuleIdentifier(moduleIdentifier, function, canonicalize, null); + } + + + /** + * Parses the given module identifier into name and optional slot elements, passing those to the given + * function and returning the result of that function. + *

+ * + * @param moduleIdentifier an identifier for a module. Cannot be {@code null} + * @param function a function to apply to the module's name and optional slot. Cannot be {@code null}. + * The slot value passed to the function may be null if the identifier does not contain one. + * @param canonicalize if {@code true} the identifier will be {@link #canonicalModuleIdentifier(String) canonicalized} before parsing + * @param defaultSlot string to pass to {@code function} as the slot parameter if the identifier doesn't include a slot value. May be {@code null} + * @return the value returned by {@code function} + * @param the type returned by {@code function} + */ + public static R parseModuleIdentifier(String moduleIdentifier, BiFunction function, + boolean canonicalize, String defaultSlot) { + if (canonicalize) { + moduleIdentifier = canonicalModuleIdentifier(moduleIdentifier); + } + // Note: this is taken from org.jboss.modules.ModuleIdentifier.fromString and lightly adapted. - if (moduleSpec == null) { + if (moduleIdentifier == null) { throw new IllegalArgumentException("Module specification is null"); - } else if (moduleSpec.isEmpty()) { + } else if (moduleIdentifier.isEmpty()) { throw new IllegalArgumentException("Empty module specification"); } else { StringBuilder b = new StringBuilder(); int c; int i; - for(i = 0; i < moduleSpec.length(); i = moduleSpec.offsetByCodePoints(i, 1)) { - c = moduleSpec.codePointAt(i); + for(i = 0; i < moduleIdentifier.length(); i = moduleIdentifier.offsetByCodePoints(i, 1)) { + c = moduleIdentifier.codePointAt(i); if (c == 92) { b.appendCodePoint(c); - i = moduleSpec.offsetByCodePoints(i, 1); - if (i >= moduleSpec.length()) { + i = moduleIdentifier.offsetByCodePoints(i, 1); + if (i >= moduleIdentifier.length()) { throw new IllegalArgumentException("Name has an unterminated escape"); } - c = moduleSpec.codePointAt(i); + c = moduleIdentifier.codePointAt(i); b.appendCodePoint(c); } else { if (c == 58) { - i = moduleSpec.offsetByCodePoints(i, 1); - if (i == moduleSpec.length()) { + i = moduleIdentifier.offsetByCodePoints(i, 1); + if (i == moduleIdentifier.length()) { throw new IllegalArgumentException("Slot is empty"); } break; @@ -58,16 +116,16 @@ public static String canonicalModuleIdentifier(String moduleSpec) { String name = b.toString(); b.setLength(0); - if (i >= moduleSpec.length()) { - return canonicalModuleIdentifier(name, null); + if (i >= moduleIdentifier.length()) { + return function.apply(name, defaultSlot); } else { do { - c = moduleSpec.codePointAt(i); + c = moduleIdentifier.codePointAt(i); b.appendCodePoint(c); - i = moduleSpec.offsetByCodePoints(i, 1); - } while(i < moduleSpec.length()); + i = moduleIdentifier.offsetByCodePoints(i, 1); + } while(i < moduleIdentifier.length()); - return canonicalModuleIdentifier(name, b.toString()); + return function.apply(name, b.toString()); } } @@ -151,4 +209,4 @@ private static String escapeSlot(String slot) { return escaped ? b.toString() : slot; } -} +} \ No newline at end of file diff --git a/controller/src/test/java/org/jboss/as/controller/ModuleIdentifierUtilUnitTestCase.java b/controller/src/test/java/org/jboss/as/controller/ModuleIdentifierUtilUnitTestCase.java new file mode 100644 index 00000000000..6f9243c5197 --- /dev/null +++ b/controller/src/test/java/org/jboss/as/controller/ModuleIdentifierUtilUnitTestCase.java @@ -0,0 +1,88 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.controller; + +import static org.jboss.as.controller.ModuleIdentifierUtil.canonicalModuleIdentifier; +import static org.jboss.as.controller.ModuleIdentifierUtil.parseModuleIdentifier; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Map; + +import org.junit.Test; + +/** + * Unit tests of {@link ModuleIdentifierUtil}. + */ +public class ModuleIdentifierUtilUnitTestCase { + + @Test + public void testParsingCanonicalization() { + assertEquals("org.jboss.foo", canonicalModuleIdentifier("org.jboss.foo")); + assertEquals("org.jboss.foo", canonicalModuleIdentifier("org.jboss.foo:main")); + assertEquals("org.jboss.foo:bar", canonicalModuleIdentifier("org.jboss.foo:bar")); + // TODO these next two seem wrong, but it's what ModuleIdentifier.fromString(...).toString() does + assertEquals("org.jboss\\\\\\:foo", canonicalModuleIdentifier("org.jboss\\:foo")); + assertEquals("org.jboss\\\\\\:foo:bar", canonicalModuleIdentifier("org.jboss\\:foo:bar")); + } + + @Test + public void testAppendingCanonicalization() { + assertEquals("org.jboss.foo", canonicalModuleIdentifier("org.jboss.foo", null)); + assertEquals("org.jboss.foo", canonicalModuleIdentifier("org.jboss.foo", "main")); + assertEquals("org.jboss.foo:bar", canonicalModuleIdentifier("org.jboss.foo", "bar")); + // TODO these next two seem wrong, but it's what ModuleIdentifier.create(...).toString() does + assertEquals("org.jboss\\\\\\:foo", canonicalModuleIdentifier("org.jboss\\:foo", null)); + assertEquals("org.jboss\\\\\\:foo:bar", canonicalModuleIdentifier("org.jboss\\:foo", "bar")); + } + + @Test + public void testParsingToFunction() { + validateFunctionResult( + parseModuleIdentifier("org.jboss.foo", ModuleIdentifierUtilUnitTestCase::biFunction), + null); + + validateFunctionResult( + parseModuleIdentifier("org.jboss.foo:main", ModuleIdentifierUtilUnitTestCase::biFunction), + "main"); + + validateFunctionResult( + parseModuleIdentifier("org.jboss.foo:main", ModuleIdentifierUtilUnitTestCase::biFunction, false), + "main"); + + validateFunctionResult( + parseModuleIdentifier("org.jboss.foo:main", ModuleIdentifierUtilUnitTestCase::biFunction, true), + null); + + validateFunctionResult( + parseModuleIdentifier("org.jboss.foo:main", ModuleIdentifierUtilUnitTestCase::biFunction, false, "bar"), + "main"); + + validateFunctionResult( + parseModuleIdentifier("org.jboss.foo:main", ModuleIdentifierUtilUnitTestCase::biFunction, true, "bar"), + "bar"); + + validateFunctionResult( + parseModuleIdentifier("org.jboss.foo", ModuleIdentifierUtilUnitTestCase::biFunction, false, "bar"), + "bar"); + + validateFunctionResult( + parseModuleIdentifier("org.jboss.foo", ModuleIdentifierUtilUnitTestCase::biFunction, true, "bar"), + "bar"); + } + + private static void validateFunctionResult(Map result, String expectedSlot) { + assertNotNull(result.toString(), result); + assertEquals(result.toString(), 1, result.size()); + assertTrue(result.toString(), result.containsKey("org.jboss.foo")); + assertEquals(result.toString(), expectedSlot == null ? "placeholder" : expectedSlot, result.get("org.jboss.foo")); + } + + private static Map biFunction(String name, String slot) { + return Map.of(name, slot == null ? "placeholder" : slot); + } +}