From 816dd486113a64900d0ceae42dcf1a6cb9d4e921 Mon Sep 17 00:00:00 2001 From: XiaoPangxie732 <47449269+XiaoPangxie732@users.noreply.github.com> Date: Mon, 3 Feb 2025 23:16:02 +0800 Subject: [PATCH] Arg relationship adjustment Mapping, Component, PairedMapping tests PairedMapping validation --- .../MinecraftDecompilerCommandLine.java | 4 +- .../mcdecompiler/mapping/Mapping.java | 8 +- .../mcdecompiler/mapping/NameGetter.java | 2 +- .../mapping/NamespacedMapping.java | 11 +- .../mcdecompiler/mapping/PairedMapping.java | 29 ++-- .../mapping/component/Descriptor.java | 3 +- .../mapping/component/LocalVariableTable.java | 27 ++-- .../mcdecompiler/mapping/component/Owned.java | 3 +- .../mcdecompiler/mapping/util/Validation.java | 27 ++++ .../test/mappings/MappingComponentTest.java | 133 ++++++++++++++++++ .../test/mappings/MappingTest.java | 124 ++++++++++++++++ 11 files changed, 322 insertions(+), 49 deletions(-) create mode 100644 modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/util/Validation.java create mode 100644 modules/mapping-api/src/test/java/cn/maxpixel/mcdecompiler/test/mappings/MappingComponentTest.java create mode 100644 modules/mapping-api/src/test/java/cn/maxpixel/mcdecompiler/test/mappings/MappingTest.java diff --git a/modules/cli/src/main/java/cn/maxpixel/mcdecompiler/MinecraftDecompilerCommandLine.java b/modules/cli/src/main/java/cn/maxpixel/mcdecompiler/MinecraftDecompilerCommandLine.java index 89a673b..5e05586 100644 --- a/modules/cli/src/main/java/cn/maxpixel/mcdecompiler/MinecraftDecompilerCommandLine.java +++ b/modules/cli/src/main/java/cn/maxpixel/mcdecompiler/MinecraftDecompilerCommandLine.java @@ -58,9 +58,9 @@ public static void main(String[] args) throws Throwable { "decompile. Values are \"CLIENT\" and \"SERVER\". With this option, you must specify --version option.") .withRequiredArg().ofType(SideType.class).defaultsTo(SideType.CLIENT); ArgumentAcceptingOptionSpec inputO = parser.acceptsAll(of("i", "input"), "Input jar.") - .requiredUnless(sideTypeO).withRequiredArg().withValuesConvertedBy(new PathConverter(PathProperties.FILE_EXISTING)); + .withRequiredArg().withValuesConvertedBy(new PathConverter(PathProperties.FILE_EXISTING)); ArgumentAcceptingOptionSpec mappingPathO = parser.acceptsAll(of("m", "map", "mapping-path"), "Mapping file that " + - "is used to deobfuscate.").requiredUnless(sideTypeO).withRequiredArg(); + "is used to deobfuscate.").withRequiredArg(); ArgumentAcceptingOptionSpec versionO = parser.acceptsAll(of("v", "ver", "version"), "Version to " + "deobfuscate/decompile. Only works on Proguard mappings or downloading libraries for the decompiler.") .requiredUnless(inputO, mappingPathO).requiredIf(sideTypeO).withRequiredArg(); diff --git a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/Mapping.java b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/Mapping.java index f5a6ad8..5bbeeb3 100644 --- a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/Mapping.java +++ b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/Mapping.java @@ -34,7 +34,7 @@ * @implNote This class should only be extended, so it is abstract */ public abstract class Mapping implements NameGetter { - private final Object2ObjectOpenHashMap, Component> components = new Object2ObjectOpenHashMap<>(); + protected final Object2ObjectOpenHashMap, Component> components = new Object2ObjectOpenHashMap<>(); /** * Constructor @@ -70,7 +70,7 @@ public C getComponent(@NotNull Class componen * @return The component if exists, or the newly created component */ @SuppressWarnings("unchecked") - public @NotNull C getOrCreateComponent(@NotNull Class component, Supplier factory) { + public @NotNull C getOrCreateComponent(@NotNull Class component, @NotNull Supplier factory) { var value = components.get(component); if (value == null) { value = Objects.requireNonNull(factory.get()); @@ -138,11 +138,11 @@ public void removeComponent(@NotNull Class component) { } /** - * Validates all the components of this mapping + * Validates this mapping * * @throws IllegalStateException If any of the component fails validation */ - public void validateComponents() throws IllegalStateException { + public void validate() throws IllegalStateException { for (Component value : components.values()) { value.validate(); } diff --git a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/NameGetter.java b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/NameGetter.java index 08bc799..e532bb9 100644 --- a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/NameGetter.java +++ b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/NameGetter.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull; /** - * Intended to be a universal interface for getting names + * Intends to be a universal interface for getting names */ public interface NameGetter { String getUnmappedName(); diff --git a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/NamespacedMapping.java b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/NamespacedMapping.java index 59224d1..5d584cf 100644 --- a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/NamespacedMapping.java +++ b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/NamespacedMapping.java @@ -20,7 +20,6 @@ import cn.maxpixel.mcdecompiler.common.util.Utils; import cn.maxpixel.mcdecompiler.mapping.component.Component; -import cn.maxpixel.mcdecompiler.mapping.component.LocalVariableTable; import cn.maxpixel.mcdecompiler.mapping.component.Owned; import cn.maxpixel.mcdecompiler.mapping.util.DescriptorRemapper; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; @@ -293,8 +292,9 @@ public NamespacedMapping setUnmappedNamespace(@NotNull String namespace) { @Override public void setMappedNamespace(@NotNull String namespace) { this.mappedNamespace = Objects.requireNonNull(namespace); - LocalVariableTable.Namespaced n = getComponent(LocalVariableTable.Namespaced.class); - if (n != null) n.setMappedNamespace(namespace); + for (Component component : getComponents()) { + if (component instanceof NameGetter.Namespace n) n.setMappedNamespace(namespace); + } } @Override @@ -305,8 +305,9 @@ public String getFallbackNamespace() { @Override public void setFallbackNamespace(@NotNull String namespace) { this.fallbackNamespace = Objects.requireNonNull(namespace); - LocalVariableTable.Namespaced n = getComponent(LocalVariableTable.Namespaced.class); - if (n != null) n.setFallbackNamespace(namespace); + for (Component component : getComponents()) { + if (component instanceof NameGetter.Namespace n) n.setFallbackNamespace(namespace); + } } /* Auto-generated equals, hashCode and toString methods */ diff --git a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/PairedMapping.java b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/PairedMapping.java index 5baa3b8..da1db39 100644 --- a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/PairedMapping.java +++ b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/PairedMapping.java @@ -20,6 +20,7 @@ import cn.maxpixel.mcdecompiler.mapping.component.Component; import cn.maxpixel.mcdecompiler.mapping.component.Owned; +import cn.maxpixel.mcdecompiler.mapping.util.Validation; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -115,23 +116,6 @@ public PairedMapping reverse() { String temp = unmappedName; unmappedName = mappedName; mappedName = temp; -// boolean supportDesc = hasComponent(Descriptor.Unmapped.class);// TODO: abstract as a general-purpose interface -// boolean supportDescMapped = hasComponent(Descriptor.Mapped.class); -// if (supportDesc) { -// Descriptor.Unmapped unmapped = getComponent(Descriptor.Unmapped.class); -// if (supportDescMapped) { -// Descriptor.Mapped mapped = getComponent(Descriptor.Mapped.class); -// String desc = unmapped.descriptor; -// unmapped.descriptor = mapped.descriptor; -// mapped.descriptor = desc; -// } else { -// addComponent(new Descriptor.Mapped(unmapped.descriptor)); -// removeComponent(Descriptor.Unmapped.class); -// } -// } else if (supportDescMapped) { -// addComponent(new Descriptor.Unmapped(getComponent(Descriptor.Mapped.class).descriptor)); -// removeComponent(Descriptor.Mapped.class); -// }// TODO: Remove this after passing the tests ObjectOpenHashSet> skipped = new ObjectOpenHashSet<>(); Object2ObjectOpenHashMap, Component> toAdd = new Object2ObjectOpenHashMap<>(); var it = getComponents().iterator(); @@ -152,9 +136,7 @@ public PairedMapping reverse() { } } } - for (Component value : toAdd.values()) { - addComponent(value); - } + components.putAll(toAdd); return this; } @@ -176,6 +158,13 @@ public void setMappedName(String mappedName) { this.mappedName = mappedName; } + @Override + public void validate() throws IllegalStateException { + Validation.requireNonNull(unmappedName, "unmappedName"); + Validation.requireNonNull(mappedName, "mappedName"); + super.validate(); + } + /* Auto-generated equals, hashCode and toString methods */ @Override public boolean equals(Object o) { diff --git a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/Descriptor.java b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/Descriptor.java index aaca842..2570653 100644 --- a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/Descriptor.java +++ b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/Descriptor.java @@ -21,6 +21,7 @@ import cn.maxpixel.mcdecompiler.common.Constants; import cn.maxpixel.mcdecompiler.common.annotation.MethodOrFieldDesc; import cn.maxpixel.mcdecompiler.mapping.util.DescriptorRemapper; +import cn.maxpixel.mcdecompiler.mapping.util.Validation; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -50,7 +51,7 @@ public void setDescriptor(@NotNull @MethodOrFieldDesc String desc) { @Override public void validate() throws IllegalStateException { - if (descriptor == null) throw new IllegalStateException("Descriptor must not be null"); + Validation.requireNonNull(descriptor, "descriptor"); if (!MATCHERS.get().reset(descriptor).matches()) { throw new IllegalStateException("Invalid descriptor: " + descriptor); } diff --git a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/LocalVariableTable.java b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/LocalVariableTable.java index 1668aaa..101de63 100644 --- a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/LocalVariableTable.java +++ b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/LocalVariableTable.java @@ -23,6 +23,7 @@ import cn.maxpixel.mcdecompiler.mapping.NamespacedMapping; import cn.maxpixel.mcdecompiler.mapping.PairedMapping; import cn.maxpixel.mcdecompiler.mapping.util.DescriptorRemapper; +import cn.maxpixel.mcdecompiler.mapping.util.Validation; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntSet; import org.jetbrains.annotations.NotNull; @@ -39,7 +40,7 @@ * will be treated as the actual lvt index - 1, which means that index 0 in {@link LocalVariableTable} represents * the index 1 in the actual lvt(omitting {@code this}). */ -public abstract class LocalVariableTable { +public abstract class LocalVariableTable implements Component { protected final @NotNull Int2ObjectOpenHashMap lvt = new Int2ObjectOpenHashMap<>(); public T getLocalVariable(@Range(from = 0, to = 255) int index) { @@ -62,6 +63,14 @@ public boolean isEmpty() { return lvt.isEmpty(); } + @Override + public void validate() throws IllegalStateException { + lvt.int2ObjectEntrySet().fastForEach(entry -> { + if (entry.getIntKey() < 0 || entry.getIntKey() > 255) throw new IllegalStateException("Illegal LVT index"); + entry.getValue().validate(); + }); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -82,14 +91,6 @@ public String toString() { } public static class Paired extends LocalVariableTable implements Component, Component.Reversible { - @Override - public void validate() throws IllegalStateException { - lvt.int2ObjectEntrySet().fastForEach(entry -> { - if (entry.getIntKey() < 0 || entry.getIntKey() > 255) throw new IllegalStateException("Illegal LVT index"); - entry.getValue().validateComponents(); - }); - } - @Override public void reverse() { lvt.values().forEach(PairedMapping::reverse); @@ -157,12 +158,8 @@ public void setFallbackNamespace(@NotNull String namespace) { @Override public void validate() throws IllegalStateException { - if (unmappedNamespace == null) throw new IllegalStateException(); - lvt.int2ObjectEntrySet().fastForEach(entry -> { - if (entry.getIntKey() < 0 || entry.getIntKey() > 255) - throw new IllegalStateException("Illegal LVT index"); - entry.getValue().validateComponents(); - }); + Validation.requireNonNull(unmappedNamespace, "unmappedNamespace"); + super.validate(); } @Override diff --git a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/Owned.java b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/Owned.java index 7222d60..2e40b51 100644 --- a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/Owned.java +++ b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/component/Owned.java @@ -20,6 +20,7 @@ import cn.maxpixel.mcdecompiler.mapping.Mapping; import cn.maxpixel.mcdecompiler.mapping.collection.ClassMapping; +import cn.maxpixel.mcdecompiler.mapping.util.Validation; import java.util.Objects; @@ -42,7 +43,7 @@ public void setOwner(ClassMapping owner) { @Override public void validate() throws IllegalStateException { - if (owner == null) throw new IllegalStateException("Owner is null"); + Validation.requireNonNull(owner, "owner"); } @Override diff --git a/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/util/Validation.java b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/util/Validation.java new file mode 100644 index 0000000..0fffc59 --- /dev/null +++ b/modules/mapping-api/src/main/java/cn/maxpixel/mcdecompiler/mapping/util/Validation.java @@ -0,0 +1,27 @@ +/* + * MinecraftDecompiler. A tool/library to deobfuscate and decompile jars. + * Copyright (C) 2019-2025 MaxPixelStudios(XiaoPangxie732) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package cn.maxpixel.mcdecompiler.mapping.util; + +import java.util.Objects; + +public class Validation { + public static void requireNonNull(Object o, String name) { + if (o == null) throw new IllegalStateException(Objects.requireNonNull(name) + " cannot be null"); + } +} \ No newline at end of file diff --git a/modules/mapping-api/src/test/java/cn/maxpixel/mcdecompiler/test/mappings/MappingComponentTest.java b/modules/mapping-api/src/test/java/cn/maxpixel/mcdecompiler/test/mappings/MappingComponentTest.java new file mode 100644 index 0000000..daa9b1f --- /dev/null +++ b/modules/mapping-api/src/test/java/cn/maxpixel/mcdecompiler/test/mappings/MappingComponentTest.java @@ -0,0 +1,133 @@ +/* + * MinecraftDecompiler. A tool/library to deobfuscate and decompile jars. + * Copyright (C) 2019-2025 MaxPixelStudios(XiaoPangxie732) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package cn.maxpixel.mcdecompiler.test.mappings; + +import cn.maxpixel.mcdecompiler.mapping.Mapping; +import cn.maxpixel.mcdecompiler.mapping.component.Component; +import cn.maxpixel.mcdecompiler.mapping.component.Descriptor; +import cn.maxpixel.mcdecompiler.mapping.component.Documented; +import cn.maxpixel.mcdecompiler.mapping.component.Owned; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class MappingComponentTest { + private static class TestMapping extends Mapping { + public TestMapping(@NotNull Component @NotNull ... components) { + super(components); + } + + public TestMapping() { + super(); + } + + @Override + @SuppressWarnings("unchecked") + protected Owned getOwned() { + return getComponent(Owned.class); + } + + @Override + public String getUnmappedName() { + return ""; + } + + @Override + public String getMappedName() { + return ""; + } + } + + @Test + void testConstructorAndGet() { + assertTrue(new TestMapping().getComponents().isEmpty()); + assertEquals(1, new TestMapping(new Descriptor.Unmapped("La;")).getComponents().size()); + assertEquals(1, new TestMapping(new Descriptor.Unmapped("La;"), + new Descriptor.Unmapped("Lb;")).getComponents().size()); + assertEquals(2, new TestMapping(new Descriptor.Unmapped("La;"), + new Descriptor.Mapped("Lb;")).getComponents().size()); + } + + @Test + void testAddRemoveHasGet() { + var du1 = new Descriptor.Unmapped("La;"); + var du2 = new Descriptor.Unmapped("Lb;"); + var dm1 = new Descriptor.Mapped("Lc;"); + var m1 = new TestMapping(); + m1.addComponent(du1); + assertTrue(m1.hasComponent(Descriptor.Unmapped.class)); + assertEquals(1, m1.getComponents().size()); + assertSame(du1, m1.getComponent(Descriptor.Unmapped.class)); + m1.addComponent(du2); + assertTrue(m1.hasComponent(Descriptor.Unmapped.class)); + assertEquals(1, m1.getComponents().size()); + assertSame(du2, m1.getComponent(Descriptor.Unmapped.class)); + m1.addComponent(dm1); + assertTrue(m1.hasComponent(Descriptor.Mapped.class)); + assertEquals(2, m1.getComponents().size()); + assertSame(dm1, m1.getComponent(Descriptor.Mapped.class)); + m1.removeComponent(Descriptor.Unmapped.class); + assertFalse(m1.hasComponent(Descriptor.Unmapped.class)); + assertTrue(m1.hasComponent(Descriptor.Mapped.class)); + assertEquals(1, m1.getComponents().size()); + assertSame(dm1, m1.getComponent(Descriptor.Mapped.class)); + } + + @Test + void testGet() { + var du1 = new Descriptor.Unmapped("La;"); + var dm1 = new Descriptor.Mapped("Lc;"); + var owned = new Owned(); + var m1 = new TestMapping(du1, dm1, owned); + var m2 = new TestMapping(du1, dm1); + assertEquals(3, m1.getComponents().size()); + assertTrue(m1.getComponents().containsAll(List.of(du1, dm1, owned))); + assertSame(du1, m1.getComponent(Descriptor.Unmapped.class)); + assertSame(owned, m1.getOwned()); + assertNull(m1.getComponent(Documented.class)); + assertNull(m2.getOwned()); + assertNotNull(m1.getComponentOptional(Documented.class)); + assertFalse(m1.getComponentOptional(Documented.class).isPresent()); + assertNotNull(m1.getComponentOptional(Descriptor.Unmapped.class)); + assertTrue(m1.getComponentOptional(Descriptor.Unmapped.class).isPresent()); + assertSame(du1, m1.getComponentOptional(Descriptor.Unmapped.class).get()); + assertThrows(NullPointerException.class, () -> m1.getOrCreateComponent(Documented.class, () -> null)); + assertNotNull(m1.getOrCreateComponent(Documented.class, Documented::new)); + assertTrue(m1.hasComponent(Documented.class)); + assertNotNull(m1.getComponent(Documented.class)); + } + + @Test + void testValidate() { + var du1 = new Descriptor.Unmapped("La"); + var du2 = new Descriptor.Unmapped("J"); + var dm1 = new Descriptor.Mapped("Lb;"); + var m = new TestMapping(du1); + assertThrows(IllegalStateException.class, m::validate); + m.addComponent(du2); + assertDoesNotThrow(m::validate); + assertDoesNotThrow(new TestMapping(du2)::validate); + assertDoesNotThrow(new TestMapping(dm1)::validate); + assertDoesNotThrow(new TestMapping(du2, dm1)::validate); + assertThrows(IllegalStateException.class, new TestMapping(du1, dm1)::validate); + } +} \ No newline at end of file diff --git a/modules/mapping-api/src/test/java/cn/maxpixel/mcdecompiler/test/mappings/MappingTest.java b/modules/mapping-api/src/test/java/cn/maxpixel/mcdecompiler/test/mappings/MappingTest.java new file mode 100644 index 0000000..b34f4db --- /dev/null +++ b/modules/mapping-api/src/test/java/cn/maxpixel/mcdecompiler/test/mappings/MappingTest.java @@ -0,0 +1,124 @@ +/* + * MinecraftDecompiler. A tool/library to deobfuscate and decompile jars. + * Copyright (C) 2019-2025 MaxPixelStudios(XiaoPangxie732) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package cn.maxpixel.mcdecompiler.test.mappings; + +import cn.maxpixel.mcdecompiler.mapping.PairedMapping; +import cn.maxpixel.mcdecompiler.mapping.component.Descriptor; +import cn.maxpixel.mcdecompiler.mapping.component.Owned; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class MappingTest { + @Test + void testPaired() { + Descriptor.Unmapped du = new Descriptor.Unmapped("La;"); + Descriptor.Mapped dm = new Descriptor.Mapped("Lb;"); + Owned o = new Owned<>(); + + PairedMapping m1 = new PairedMapping(); + assertNull(m1.unmappedName); + assertNull(m1.mappedName); + assertTrue(m1.getComponents().isEmpty()); + assertThrows(IllegalStateException.class, m1::validate); + + PairedMapping m2 = new PairedMapping(du); + assertNull(m2.unmappedName); + assertNull(m2.mappedName); + assertEquals(1, m2.getComponents().size()); + assertThrows(IllegalStateException.class, m2::validate); + + PairedMapping m3 = new PairedMapping("a"); + assertEquals("a", m3.unmappedName); + assertEquals("a", m3.mappedName); + assertTrue(m3.getComponents().isEmpty()); + assertDoesNotThrow(m3::validate); + + PairedMapping m4 = new PairedMapping("a", du); + assertEquals("a", m4.unmappedName); + assertEquals("a", m4.mappedName); + assertEquals(1, m4.getComponents().size()); + + PairedMapping m5 = new PairedMapping("a", "b"); + assertEquals("a", m5.unmappedName); + assertEquals("b", m5.mappedName); + assertTrue(m5.getComponents().isEmpty()); + + PairedMapping m6 = new PairedMapping("a", "b", du); + assertEquals("a", m6.unmappedName); + assertEquals("b", m6.mappedName); + assertEquals(1, m6.getComponents().size()); + + m1.unmappedName = "a"; + m1.mappedName = "b"; + assertEquals("a", m1.unmappedName); + assertEquals("b", m1.mappedName); + assertSame(m1.unmappedName, m1.getUnmappedName()); + assertSame(m1.mappedName, m1.getMappedName()); + assertDoesNotThrow(m1::validate); + + m2.setUnmappedName("a"); + m2.setMappedName("b"); + assertEquals("a", m2.getUnmappedName()); + assertEquals("b", m2.getMappedName()); + assertSame(m2.unmappedName, m2.getUnmappedName()); + assertSame(m2.mappedName, m2.getMappedName()); + assertDoesNotThrow(m2::validate); + + assertSame(o, new PairedMapping(o).getOwned()); + + m6.reverse(); + assertTrue(m6.hasComponent(Descriptor.Mapped.class)); + assertEquals("La;", m6.getComponent(Descriptor.Mapped.class).descriptor); + assertEquals("b", m6.unmappedName); + assertEquals("a", m6.mappedName); + assertEquals(1, m6.getComponents().size()); + + m6.reverse(); + assertTrue(m6.hasComponent(Descriptor.Unmapped.class)); + assertEquals("La;", m6.getComponent(Descriptor.Unmapped.class).descriptor); + assertEquals("a", m6.unmappedName); + assertEquals("b", m6.mappedName); + assertEquals(1, m6.getComponents().size()); + + PairedMapping m7 = new PairedMapping("a", "b", du, dm); + assertEquals(2, m7.getComponents().size()); + m7.reverse(); + assertEquals(2, m7.getComponents().size()); + assertTrue(m7.hasComponent(Descriptor.Unmapped.class)); + assertTrue(m7.hasComponent(Descriptor.Mapped.class)); + assertEquals("La;", m7.getComponent(Descriptor.Mapped.class).descriptor); + assertEquals("Lb;", m7.getComponent(Descriptor.Unmapped.class).descriptor); + assertEquals("b", m7.unmappedName); + assertEquals("a", m7.mappedName); + m7.reverse(); + assertEquals(2, m7.getComponents().size()); + assertTrue(m7.hasComponent(Descriptor.Unmapped.class)); + assertTrue(m7.hasComponent(Descriptor.Mapped.class)); + assertEquals("La;", m7.getComponent(Descriptor.Unmapped.class).descriptor); + assertEquals("Lb;", m7.getComponent(Descriptor.Mapped.class).descriptor); + assertEquals("a", m7.unmappedName); + assertEquals("b", m7.mappedName); + } + + @Test + void testNamespaced() { + + } +} \ No newline at end of file