From d494a3d27389ab931d344e83333f1f72f88da0dc Mon Sep 17 00:00:00 2001 From: stefvanschie Date: Fri, 4 Jun 2021 20:59:53 +0200 Subject: [PATCH] Add pattern pane --- .../inventoryframework/gui/type/util/Gui.java | 1 + .../inventoryframework/pane/PatternPane.java | 425 ++++++++++++++++++ .../inventoryframework/pane/util/Pattern.java | 315 +++++++++++++ .../pane/PatternPaneTest.java | 52 +++ .../pane/util/PatternTest.java | 139 ++++++ 5 files changed, 932 insertions(+) create mode 100644 IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PatternPane.java create mode 100644 IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Pattern.java create mode 100644 IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/PatternPaneTest.java create mode 100644 IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/PatternTest.java diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/Gui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/Gui.java index ef83947a..02885729 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/Gui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/Gui.java @@ -645,6 +645,7 @@ public static Pane loadPane(@NotNull Object instance, @NotNull Node node) { registerPane("masonrypane", MasonryPane::load); registerPane("outlinepane", OutlinePane::load); registerPane("paginatedpane", PaginatedPane::load); + registerPane("patternpane", PatternPane::load); registerPane("staticpane", StaticPane::load); registerPane("cyclebutton", CycleButton::load); diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PatternPane.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PatternPane.java new file mode 100644 index 00000000..4adde932 --- /dev/null +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PatternPane.java @@ -0,0 +1,425 @@ +package com.github.stefvanschie.inventoryframework.pane; + +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; +import com.github.stefvanschie.inventoryframework.pane.util.Pattern; +import com.github.stefvanschie.inventoryframework.util.GeometryUtil; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.*; + +/** + * A pattern pane allows you to specify a textual pattern and assign items to individual characters. + * + * @since 0.9.8 + */ +public class PatternPane extends Pane implements Flippable, Rotatable { + + /** + * The pattern of this pane. + */ + @NotNull + private Pattern pattern; + + /** + * The bindings between the characters in the pattern and the gui item. Not every character in the pattern has to be + * present in this map and this map may contain characters that are not present in the pattern. + */ + @NotNull + private final Map bindings = new HashMap<>(); + + /** + * The amount of degrees this pane is rotated by. This will always be between [0,360) and a multiple of 90. + */ + private int rotation; + + /** + * Whether this pane is flipped horizontally. + */ + private boolean flippedHorizontally; + + /** + * Whether this pane is flipped vertically. + */ + private boolean flippedVertically; + + /** + * Constructs a new pattern pane. + * + * @param x the upper left x coordinate of the pane + * @param y the upper left y coordinate of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param priority the priority of the pane + * @param pattern the pattern of the pane + * @throws IllegalArgumentException when the pane and pattern dimensions don't match + * @since 0.9.8 + */ + public PatternPane(int x, int y, int length, int height, @NotNull Priority priority, @NotNull Pattern pattern) { + super(x, y, length, height, priority); + + if (pattern.getLength() != length || pattern.getHeight() != height) { + throw new IllegalArgumentException( + "Dimensions of the provided pattern do not match the dimensions of the pane" + ); + } + + this.pattern = pattern; + } + + /** + * Constructs a new pattern pane, with no position. + * + * @param length the length of the pane + * @param height the height of the pane + * @param pattern the pattern of the pane + * @throws IllegalArgumentException when the pane and pattern dimensions don't match + * @since 0.9.8 + */ + public PatternPane(int length, int height, @NotNull Pattern pattern) { + this(0, 0, length, height, pattern); + } + + /** + * Constructs a new pattern pane. + * + * @param x the upper left x coordinate of the pane + * @param y the upper left y coordinate of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param pattern the pattern of the pane + * @throws IllegalArgumentException when the pane and pattern dimensions don't match + * @since 0.9.8 + */ + public PatternPane(int x, int y, int length, int height, @NotNull Pattern pattern) { + this(x, y, length, height, Priority.NORMAL, pattern); + } + + @Override + public void display(@NotNull InventoryComponent inventoryComponent, int paneOffsetX, int paneOffsetY, int maxLength, + int maxHeight) { + int length = Math.min(this.length, maxLength); + int height = Math.min(this.height, maxHeight); + + for (int x = 0; x < length; x++) { + for (int y = 0; y < height; y++) { + GuiItem item = this.bindings.get(pattern.getCharacter(x, y)); + + if (item == null || !item.isVisible()) { + continue; + } + + int newX = x, newY = y; + + if (isFlippedHorizontally()) { + newX = length - x - 1; + } + + if (isFlippedVertically()) { + newY = height - y - 1; + } + + Map.Entry coordinates = GeometryUtil.processClockwiseRotation(newX, newY, length, + height, rotation); + + newX = coordinates.getKey(); + newY = coordinates.getValue(); + + int finalRow = getY() + newY + paneOffsetY; + int finalColumn = getX() + newX + paneOffsetX; + + inventoryComponent.setItem(item, finalColumn, finalRow); + } + } + } + + @Override + public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComponent, + @NotNull InventoryClickEvent event, int slot, int paneOffsetX, int paneOffsetY, int maxLength, + int maxHeight) { + int length = Math.min(this.length, maxLength); + int height = Math.min(this.height, maxHeight); + + int adjustedSlot = slot - (getX() + paneOffsetX) - inventoryComponent.getLength() * (getY() + paneOffsetY); + + int x = adjustedSlot % inventoryComponent.getLength(); + int y = adjustedSlot / inventoryComponent.getLength(); + + //this isn't our item + if (x < 0 || x >= length || y < 0 || y >= height) { + return false; + } + + callOnClick(event); + + ItemStack itemStack = event.getCurrentItem(); + + if (itemStack == null) { + return false; + } + + GuiItem clickedItem = findMatchingItem(getItems(), itemStack); + + if (clickedItem == null) { + return false; + } + + clickedItem.callAction(event); + + return true; + } + + @NotNull + @Contract(pure = true) + @Override + public PatternPane copy() { + PatternPane patternPane = new PatternPane(getX(), getY(), getLength(), getHeight(), getPriority(), getPattern()); + + patternPane.setVisible(isVisible()); + patternPane.onClick = onClick; + + patternPane.uuid = uuid; + + patternPane.setRotation(getRotation()); + patternPane.flipHorizontally(isFlippedHorizontally()); + patternPane.flipVertically(isFlippedVertically()); + + return patternPane; + } + + @Override + public void setRotation(int rotation) { + if (getLength() != getHeight()) { + throw new IllegalArgumentException("Rotations can only be applied to square panes"); + } + + if (rotation >= 0 && rotation % 90 != 0) { + throw new IllegalArgumentException("Rotation must be non-negative and be a multiple of 90"); + } + + this.rotation = rotation % 360; + } + + /** + * {@inheritDoc} + * + * This only returns the items for which their binding also appears in the given pattern. An item bound to 'x', + * where 'x' does not appear in the pattern will not be returned. + * + * @return the bounded and used items + * @since 0.9.8 + */ + @NotNull + @Override + public Collection getItems() { + Set items = new HashSet<>(); + + for (Map.Entry binding : bindings.entrySet()) { + if (pattern.contains(binding.getKey())) { + items.add(binding.getValue()); + } + } + + return Collections.unmodifiableCollection(items); + } + + /** + * Overrides the pattern set on this pane. + * + * @param pattern the new pattern to set + * @throws IllegalArgumentException when the pane and pattern dimensions don't match + * @since 0.9.8 + */ + public void setPattern(@NotNull Pattern pattern) { + if (pattern.getLength() != getLength() || pattern.getHeight() != getHeight()) { + throw new IllegalArgumentException( + "Dimensions of the provided pattern do not match the dimensions of the pane" + ); + } + + this.pattern = pattern; + } + + @Override + public void setHeight(int height) { + super.setHeight(height); + + this.pattern = this.pattern.setHeight(height); + } + + @Override + public void setLength(int length) { + super.setLength(length); + + this.pattern = this.pattern.setLength(length); + } + + /** + * Binds a character to a specific item or if the character was already bound, this overwrites the previously + * binding with the provided one. To bind characters above the 16-bit range, see {@link #bindItem(int, GuiItem)}. + * + * @param character the character + * @param item the item this represents + * @since 0.9.8 + */ + public void bindItem(char character, @NotNull GuiItem item) { + this.bindings.put((int) character, item); + } + + /** + * Binds a character to a specific item or if the character was already bound, this overwrites the previously + * binding with the provided one. + * + * @param character the character + * @param item the item this represents + * @since 0.9.8 + * @see PatternPane#bindItem(char, GuiItem) + */ + public void bindItem(int character, @NotNull GuiItem item) { + this.bindings.put(character, item); + } + + @Override + public void clear() { + this.bindings.clear(); + } + + @Override + public void flipHorizontally(boolean flipHorizontally) { + this.flippedHorizontally = flipHorizontally; + } + + @Override + public void flipVertically(boolean flipVertically) { + this.flippedVertically = flipVertically; + } + + @NotNull + @Override + public Collection getPanes() { + return Collections.emptySet(); + } + + /** + * Gets the pattern. + * + * @return the pattern + * @since 0.9.8 + */ + @NotNull + @Contract(pure = true) + public Pattern getPattern() { + return pattern; + } + + @Override + public boolean isFlippedHorizontally() { + return this.flippedHorizontally; + } + + @Override + public boolean isFlippedVertically() { + return this.flippedVertically; + } + + @Override + public int getRotation() { + return this.rotation; + } + + /** + * Loads a pattern pane from a given element + * + * @param instance the instance class + * @param element the element + * @return the pattern pane + */ + @NotNull + public static PatternPane load(@NotNull Object instance, @NotNull Element element) { + try { + NodeList childNodes = element.getChildNodes(); + + Pattern pattern = null; + Map bindings = new HashMap<>(); + + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.item(i); + + if (item.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element child = (Element) item; + String name = item.getNodeName(); + + if (name.equals("pattern")) { + pattern = Pattern.load(child); + } else if (name.equals("binding")) { + String character = child.getAttribute("char"); + + if (character == null) { + throw new XMLLoadException("Missing char attribute on binding"); + } + + if (character.codePointCount(0, character.length()) != 1) { + throw new XMLLoadException("Char attribute doesn't have one character"); + } + + NodeList children = child.getChildNodes(); + GuiItem guiItem = null; + + for (int index = 0; index < children.getLength(); index++) { + Node guiItemNode = children.item(index); + + if (guiItemNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + if (guiItem != null) { + throw new XMLLoadException("Binding has multiple inner tags, one expected"); + } + + guiItem = Pane.loadItem(instance, (Element) guiItemNode); + } + + //guaranteed to only be a single code point + bindings.put(character.codePoints().toArray()[0], guiItem); + } else { + throw new XMLLoadException("Unknown tag " + name + " in pattern pane"); + } + } + + if (pattern == null) { + throw new XMLLoadException("Pattern pane doesn't have a pattern"); + } + + PatternPane patternPane = new PatternPane( + Integer.parseInt(element.getAttribute("length")), + Integer.parseInt(element.getAttribute("height")), + pattern + ); + + Pane.load(patternPane, instance, element); + Flippable.load(patternPane, element); + Rotatable.load(patternPane, element); + + if (!element.hasAttribute("populate")) { + for (Map.Entry entry : bindings.entrySet()) { + patternPane.bindItem(entry.getKey(), entry.getValue()); + } + } + + return patternPane; + } catch (NumberFormatException exception) { + throw new XMLLoadException(exception); + } + } +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Pattern.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Pattern.java new file mode 100644 index 00000000..7539be0e --- /dev/null +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Pattern.java @@ -0,0 +1,315 @@ +package com.github.stefvanschie.inventoryframework.pane.util; + +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.pane.PatternPane; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A mask for {@link PatternPane}s that specifies in which positions the items should be placed. Objects of this class + * are immutable. + * + * @since 0.9.8 + */ +public class Pattern { + + /** + * A two-dimensional array of characters indicating which slot has which character. The characters are stored as + * integers to properly support characters outside the 16-bit range. This means that, what would be a surrogate pair + * in a string, is now a single number. This two-dimensional array is constructed in a row-major order fashion. + */ + private final int @NotNull [] @NotNull [] pattern; + + /** + * Creates a pattern based on the strings provided. Each string is a row for the pattern and each character is a + * slot of that row. When multiple strings have a different length an {@link IllegalArgumentException} will be + * thrown. + * + * Surrogate pairs in strings are treated as a single character and not as two. This means that a string with five + * surrogate pairs, will count as having five characters, not ten. + * + * @param pattern a var-arg of strings that represent this pattern + * @throws IllegalArgumentException when strings have different lengths + * @since 0.9.8 + */ + public Pattern(@NotNull String @NotNull ... pattern) { + int rows = pattern.length; + boolean zeroRows = rows == 0; + + this.pattern = new int[rows][zeroRows ? 0 : pattern[0].codePointCount(0, pattern[0].length())]; + + if (zeroRows) { + return; + } + + int globalLength = this.pattern[0].length; + + for (int index = 0; index < rows; index++) { + String row = pattern[index]; + int length = row.codePointCount(0, row.length()); + + if (length != globalLength) { + throw new IllegalArgumentException( + "Rows have different lengths, row 1 has " + globalLength + " characters, but row " + index + + " has " + length + " characters" + ); + } + + List values = new ArrayList<>(); + + row.codePoints().forEach(values::add); + + for (int column = 0; column < values.size(); column++) { + this.pattern[index][column] = values.get(column); + } + } + } + + /** + * Creates a pattern based on the two-dimensional int array provided. Each array is a row for the pattern and each + * index is a cell of that row that indicates a slot for the pattern. + * + * @param pattern a two-dimensional int array that represent this pattern + * @since 0.9.8 + */ + private Pattern(int @NotNull [] @NotNull [] pattern) { + this.pattern = pattern; + } + + /** + * Creates a new pattern with the specified height. If the new height is smaller than the previous height, the + * excess values will be truncated. If the new height is longer than the previous height, additional values will be + * added which are the same as the current bottom row. If the height is the same as the previous pattern, this will + * simply return a new pattern identical to this one. + * + * @param height the new height of the pattern + * @return a new pattern with the specified height + * @since 0.9.8 + */ + @NotNull + @Contract(pure = true) + public Pattern setHeight(int height) { + int[][] newRows = new int[height][getLength()]; + + for (int index = 0; index < Math.min(height, getHeight()); index++) { + System.arraycopy(pattern[index], 0, newRows[index], 0, pattern[index].length); + } + + for (int index = Math.min(height, getHeight()); index < height; index++) { + int[] previousRow = newRows[index - 1]; + + newRows[index] = Arrays.copyOf(previousRow, previousRow.length); + } + + return new Pattern(newRows); + } + + /** + * Creates a new pattern with the specified length. If the new length is smaller than the previous length, the excess + * values will be truncated. If the new length is longer than the previous length, additional values will be added + * which are the same as the rightmost value on a given row. If the length is the same as the previous pattern, this + * will simply return a new pattern identical to this one. + * + * @param length the new length of the pattern + * @return a new pattern with the specified length + * @since 0.9.8 + */ + @NotNull + @Contract(pure = true) + public Pattern setLength(int length) { + int[][] newRows = new int[getHeight()][length]; + + for (int index = 0; index < pattern.length; index++) { + int[] newRow = new int[length]; + int[] row = pattern[index]; + int minLength = Math.min(length, row.length); + + System.arraycopy(row, 0, newRow, 0, minLength); + + for (int column = minLength; column < length; column++) { + newRow[column] = newRow[minLength - 1]; + } + + newRows[index] = newRow; + } + + return new Pattern(newRows); + } + + /** + * Gets the column of this pattern at the specified index. The values indicate the character of the slots for that + * slot. The returned array is a copy of the original; modifications to the returned array will not be reflected in + * the pattern. + * + * @param index the column index + * @return the column of this pattern + * @throws IllegalArgumentException when the index is outside the pattern's range + * @since 0.9.8 + */ + @Contract(pure = true) + public int @NotNull [] getColumn(int index) { + if (index >= getLength()) { + throw new IllegalArgumentException("Index " + index + " exceeds pattern length"); + } + + int[] column = new int[pattern[0].length]; + + for (int i = 0; i < getHeight(); i++) { + column[i] = pattern[i][index]; + } + + return column; + } + + /** + * Gets whether the provided character is present in this pattern. For checking surrogate pairs, the pair should be + * combined into one single number. + * + * @param character the character to look for + * @return whether the provided character is present + * @since 0.9.8 + */ + @Contract(pure = true) + public boolean contains(int character) { + for (int[] row : pattern) { + for (int cell : row) { + if (cell != character) { + continue; + } + + return true; + } + } + + return false; + } + + /** + * Gets the row of this mask at the specified index. The values indicate the character of the slots for that + * slot. The returned array is a copy of the original; modifications to the returned array will not be reflected in + * the pattern. + * + * @param index the row index + * @return the row of this pattern + * @throws IllegalArgumentException when the index is outside the pattern's range + * @since 0.9.8 + */ + @Contract(pure = true) + public int @NotNull [] getRow(int index) { + if (index >= getHeight()) { + throw new IllegalArgumentException("Index " + index + " exceeds pattern height"); + } + + int[] row = pattern[index]; + + return Arrays.copyOf(row, row.length); + } + + /** + * Gets the character at the specified position. This returns an integer, instead of a character to properly account + * for characters above the 16-bit range. + * + * @param x the x position + * @param y the y position + * @return the character at the specified position + * @throws IllegalArgumentException when the position is out of range + * @since 0.9.8 + */ + @Contract(pure = true) + public int getCharacter(int x, int y) { + if (x < 0 || x >= getLength() || y < 0 || y >= getHeight()) { + throw new IllegalArgumentException("Position " + x + ", " + y + " is out of range"); + } + + return this.pattern[y][x]; + } + + /** + * Gets the length of this pattern + * + * @return the length + * @since 0.9.8 + */ + @Contract(pure = true) + public int getLength() { + return pattern[0].length; + } + + /** + * Gets the height of this pattern + * + * @return the height + * @since 0.9.8 + */ + @Contract(pure = true) + public int getHeight() { + return pattern.length; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + + if (object == null || getClass() != object.getClass()) { + return false; + } + + Pattern pattern = (Pattern) object; + + return Arrays.deepEquals(this.pattern, pattern.pattern); + } + + @Override + public int hashCode() { + return Arrays.deepHashCode(pattern); + } + + @Override + public String toString() { + return "Mask{" + + "mask=" + Arrays.deepToString(pattern) + + '}'; + } + + /** + * Loads a pattern from an xml element. + * + * @param element the xml element + * @return the loaded pattern + * @since 0.9.8 + */ + @NotNull + @Contract(pure = true) + public static Pattern load(@NotNull Element element) { + ArrayList rows = new ArrayList<>(); + NodeList childNodes = element.getChildNodes(); + + for (int itemIndex = 0; itemIndex < childNodes.getLength(); itemIndex++) { + Node item = childNodes.item(itemIndex); + + if (item.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element child = (Element) item; + String name = child.getNodeName(); + + if (!name.equals("row")) { + throw new XMLLoadException("Pattern contains unknown tag " + name); + } + + rows.add(child.getTextContent()); + } + + return new Pattern(rows.toArray(new String[0])); + } +} diff --git a/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/PatternPaneTest.java b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/PatternPaneTest.java new file mode 100644 index 00000000..a9f2fc85 --- /dev/null +++ b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/PatternPaneTest.java @@ -0,0 +1,52 @@ +package com.github.stefvanschie.inventoryframework.pane; + +import com.github.stefvanschie.inventoryframework.pane.util.Pattern; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class PatternPaneTest { + + @Test + void testApplyPatternInvalidDimensions() { + assertThrows(IllegalArgumentException.class, () -> + new PatternPane(3, 7, new Pattern("0", "1"))); + + PatternPane pane = new PatternPane(3, 7, new Pattern( + "000", + "000", + "000", + "000", + "000", + "000", + "000" + )); + + assertThrows(IllegalArgumentException.class, () -> pane.setPattern(new Pattern("0"))); + } + + @Test + void testCopy() { + PatternPane original = new PatternPane(8, 5, 1, 1, Pane.Priority.HIGHEST, new Pattern("1")); + original.setVisible(false); + original.setRotation(180); + original.flipHorizontally(true); + original.flipVertically(true); + + PatternPane copy = original.copy(); + + assertNotSame(original, copy); + + assertEquals(original.getX(), copy.getX()); + assertEquals(original.getY(), copy.getY()); + assertEquals(original.getLength(), copy.getLength()); + assertEquals(original.getHeight(), copy.getHeight()); + assertEquals(original.getPriority(), copy.getPriority()); + assertEquals(original.isVisible(), copy.isVisible()); + assertEquals(original.getRotation(), copy.getRotation()); + assertEquals(original.isFlippedHorizontally(), copy.isFlippedHorizontally()); + assertEquals(original.isFlippedVertically(), copy.isFlippedVertically()); + assertEquals(original.getPattern(), copy.getPattern()); + assertEquals(original.getUUID(), copy.getUUID()); + } +} diff --git a/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/PatternTest.java b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/PatternTest.java new file mode 100644 index 00000000..285c0abf --- /dev/null +++ b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/PatternTest.java @@ -0,0 +1,139 @@ +package com.github.stefvanschie.inventoryframework.pane.util; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.ThrowingSupplier; + +import static org.junit.jupiter.api.Assertions.*; + +public class PatternTest { + + @Test + void testGetCharacter() { + Pattern pattern = new Pattern( + "12", + "34" + ); + + assertEquals('4', pattern.getCharacter(1, 1)); + } + + @Test + void testCodePoints() { + Pattern pattern = new Pattern("\uD800\uDC00"); //surrogate pair for U+10000; Linear B Syllable B008 A + + assertEquals(1, pattern.getLength()); + } + + @Test + void testConstructorInvalidLengths() { + assertThrows(IllegalArgumentException.class, () -> new Pattern( + "0", + "0000000000" + )); + } + + @Test + void testConstructorAcceptEmpty() { + assertDoesNotThrow((ThrowingSupplier) Pattern::new); + } + + @Test + void testGetColumn() { + assertArrayEquals(new int[] {'1', '0'}, new Pattern( + "10", + "00" + ).getColumn(0)); + } + + @Test + void testGetRow() { + assertArrayEquals(new int[] {'1', '0'}, new Pattern( + "10", + "00" + ).getRow(0)); + } + + @Test + void testGetLength() { + assertEquals(2, new Pattern( + "10", + "00", + "01" + ).getLength()); + } + + @Test + void testGetHeight() { + assertEquals(3, new Pattern( + "10", + "00", + "01" + ).getHeight()); + } + + @Test + void testSetLengthLonger() { + assertEquals(new Pattern( + "100", + "011" + ), new Pattern( + "10", + "01" + ).setLength(3)); + } + + @Test + void testSetLengthShorter() { + assertEquals(new Pattern( + "1", + "0" + ), new Pattern( + "10", + "01" + ).setLength(1)); + } + + @Test + void testSetLengthEqual() { + assertEquals(new Pattern( + "10", + "01" + ), new Pattern( + "10", + "01" + ).setLength(2)); + } + + @Test + void testSetHeightLonger() { + assertEquals(new Pattern( + "10", + "01", + "01" + ), new Pattern( + "10", + "01" + ).setHeight(3)); + } + + @Test + void testSetHeightSmaller() { + assertEquals(new Pattern( + "10" + ), new Pattern( + "10", + "01" + ).setHeight(1)); + } + + @Test + void testSetHeightEqual() { + assertEquals(new Pattern( + "10", + "01" + ), new Pattern( + "10", + "01" + ).setHeight(2)); + } +}