From fa3dbd16a664c54edd2fcb92dcd2703a9883e092 Mon Sep 17 00:00:00 2001 From: Greg Gibeling Date: Tue, 24 Sep 2024 19:54:56 -0700 Subject: [PATCH 1/2] G2-1646 Research & implement standard path operations --- .../collection/CollectionCollection.java | 9 +- .../core/error/IllegalOperationException.java | 19 +++ .../g2forge/alexandria/path/path/IPath.java | 60 +++++++++- .../g2forge/alexandria/path/path/Path.java | 5 - .../alexandria/path/path/TestPath.java | 109 ++++++++++++++++++ 5 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 ax-java/src/main/java/com/g2forge/alexandria/java/core/error/IllegalOperationException.java diff --git a/ax-collection/src/main/java/com/g2forge/alexandria/collection/CollectionCollection.java b/ax-collection/src/main/java/com/g2forge/alexandria/collection/CollectionCollection.java index 7b1ccb1d..82dff729 100644 --- a/ax-collection/src/main/java/com/g2forge/alexandria/collection/CollectionCollection.java +++ b/ax-collection/src/main/java/com/g2forge/alexandria/collection/CollectionCollection.java @@ -3,6 +3,9 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; import java.util.stream.Stream; import com.g2forge.alexandria.java.core.helpers.HCollection; @@ -39,6 +42,10 @@ public Stream stream() { @Override public Collection toCollection() { - return Collections.unmodifiableCollection(getElements()); + final Collection elements = getElements(); + if (elements instanceof List) return Collections.unmodifiableList((List) elements); + if (elements instanceof SortedSet) return Collections.unmodifiableSet((SortedSet) elements); + if (elements instanceof Set) return Collections.unmodifiableSet((Set) elements); + return Collections.unmodifiableCollection(elements); } } diff --git a/ax-java/src/main/java/com/g2forge/alexandria/java/core/error/IllegalOperationException.java b/ax-java/src/main/java/com/g2forge/alexandria/java/core/error/IllegalOperationException.java new file mode 100644 index 00000000..520cbfd8 --- /dev/null +++ b/ax-java/src/main/java/com/g2forge/alexandria/java/core/error/IllegalOperationException.java @@ -0,0 +1,19 @@ +package com.g2forge.alexandria.java.core.error; + +public class IllegalOperationException extends Error { + private static final long serialVersionUID = -49635096423947391L; + + public IllegalOperationException() {} + + public IllegalOperationException(String message) { + super(message); + } + + public IllegalOperationException(String message, Throwable cause) { + super(message, cause); + } + + public IllegalOperationException(Throwable cause) { + super(cause); + } +} diff --git a/ax-path/src/main/java/com/g2forge/alexandria/path/path/IPath.java b/ax-path/src/main/java/com/g2forge/alexandria/path/path/IPath.java index 539798fc..c254c61c 100644 --- a/ax-path/src/main/java/com/g2forge/alexandria/path/path/IPath.java +++ b/ax-path/src/main/java/com/g2forge/alexandria/path/path/IPath.java @@ -1,11 +1,69 @@ package com.g2forge.alexandria.path.path; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + import com.g2forge.alexandria.collection.ICollection; +import com.g2forge.alexandria.java.adt.compare.CollectionComparator; +import com.g2forge.alexandria.java.core.error.IllegalOperationException; +import com.g2forge.alexandria.java.core.helpers.HCollection; public interface IPath { + public static class PathComparator> implements Comparator

{ + protected final CollectionComparator> collectionComparator; + + public PathComparator(final Comparator componentComparator) { + this.collectionComparator = new CollectionComparator<>(componentComparator); + } + + @Override + public int compare(P c1, P c2) { + return collectionComparator.compare(c1.getComponents().toCollection(), c2.getComponents().toCollection()); + } + } + + public default boolean endsWith(IPath other) { + final int thisSize = size(); + final int thatSize = other.size(); + if (thisSize < thatSize) return false; + final List thisEnding = HCollection.asList(getComponents().toCollection()).subList(thisSize - thatSize, thisSize); + final List thatList = HCollection.asList(other.getComponents().toCollection()); + return thisEnding.equals(thatList); + } + + public default T getComponent(int index) { + return HCollection.get(getComponents().toCollection(), index); + } + public ICollection getComponents(); + public default IPath getParent() { + if (isEmpty()) throw new IllegalOperationException(); + final List list = HCollection.asList(getComponents().toCollection()); + return new Path<>(list.subList(0, list.size() - 1)); + } + + public default boolean isEmpty() { + return getComponents().isEmpty(); + } + public IPath resolve(IPath subpath); - public boolean isEmpty(); + public default IPath resolveSibling(IPath subpath) { + return getParent().resolve(subpath); + } + + public default int size() { + if (isEmpty()) return 0; + return getComponents().toCollection().size(); + } + + public default boolean startsWith(IPath other) { + final int thisSize = size(); + final int thatSize = other.size(); + if (thisSize < thatSize) return false; + final List beginning = HCollection.asList(getComponents().toCollection()).subList(0, thatSize); + return beginning.equals(other.getComponents().toCollection()); + } } diff --git a/ax-path/src/main/java/com/g2forge/alexandria/path/path/Path.java b/ax-path/src/main/java/com/g2forge/alexandria/path/path/Path.java index 99b621c9..29da997f 100644 --- a/ax-path/src/main/java/com/g2forge/alexandria/path/path/Path.java +++ b/ax-path/src/main/java/com/g2forge/alexandria/path/path/Path.java @@ -33,11 +33,6 @@ public Path(T... components) { this(components.length < 1 ? EmptyCollection.create() : new CollectionCollection<>(components)); } - @Override - public boolean isEmpty() { - return getComponents().isEmpty(); - } - @Override public IPath resolve(IPath subpath) { if (isEmpty()) return subpath; diff --git a/ax-path/src/test/java/com/g2forge/alexandria/path/path/TestPath.java b/ax-path/src/test/java/com/g2forge/alexandria/path/path/TestPath.java index 444d8a09..4bfee5f3 100644 --- a/ax-path/src/test/java/com/g2forge/alexandria/path/path/TestPath.java +++ b/ax-path/src/test/java/com/g2forge/alexandria/path/path/TestPath.java @@ -2,9 +2,68 @@ import org.junit.Test; +import com.g2forge.alexandria.java.adt.compare.ComparableComparator; +import com.g2forge.alexandria.java.core.error.IllegalOperationException; +import com.g2forge.alexandria.path.path.IPath.PathComparator; import com.g2forge.alexandria.test.HAssert; public class TestPath { + @Test + public void endsWithA() { + HAssert.assertTrue(new Path<>("a").endsWith(new Path<>("a"))); + } + + @Test + public void endsWithAB() { + HAssert.assertTrue(new Path<>("a", "b").endsWith(new Path<>("b"))); + } + + @Test + public void endsWithAC() { + HAssert.assertFalse(new Path<>("a", "b").endsWith(new Path<>("a", "c"))); + } + + @Test + public void endsWithB() { + HAssert.assertFalse(new Path<>("a").endsWith(new Path<>("b"))); + } + + @Test + public void endsWithShort() { + HAssert.assertFalse(new Path<>("a").endsWith(new Path<>("a", "b"))); + } + + @Test + public void getParent() { + HAssert.assertEquals(new Path<>("a"), new Path<>("a", "b").getParent()); + } + + @Test(expected = IllegalOperationException.class) + public void getParentIllegal() { + Path.createEmpty().getParent(); + } + + @Test + public void pathComparator_1() { + final PathComparator> pathComparator = new IPath.PathComparator<>(new ComparableComparator<>()); + HAssert.assertEquals(-1, pathComparator.compare(new Path<>("a"), new Path<>("a", "b"))); + HAssert.assertEquals(-1, pathComparator.compare(new Path<>("a"), new Path<>("b"))); + } + + @Test + public void pathComparator0() { + final PathComparator> pathComparator = new IPath.PathComparator<>(new ComparableComparator<>()); + HAssert.assertEquals(0, pathComparator.compare(new Path<>("a"), new Path<>("a"))); + HAssert.assertEquals(0, pathComparator.compare(Path.createEmpty(), Path.createEmpty())); + } + + @Test + public void pathComparator1() { + final PathComparator> pathComparator = new IPath.PathComparator<>(new ComparableComparator<>()); + HAssert.assertEquals(1, pathComparator.compare(new Path<>("b"), new Path<>("a", "b"))); + HAssert.assertEquals(1, pathComparator.compare(new Path<>("a", "b"), new Path<>("a"))); + } + @Test public void resolve0A() { final Path a = new Path<>("a"); @@ -21,4 +80,54 @@ public void resolveA0() { public void resolveAB() { HAssert.assertEquals(new Path<>("a", "b"), new Path<>("a").resolve(new Path<>("b"))); } + + @Test + public void resolveSibling() { + HAssert.assertEquals(new Path<>("a", "c"), new Path<>("a", "b").resolveSibling(new Path<>("c"))); + } + + @Test(expected = IllegalOperationException.class) + public void resolveSiblingIllegal() { + Path.createEmpty().resolveSibling(new Path<>("a")); + } + + @Test + public void size0() { + HAssert.assertEquals(0, Path.createEmpty().size()); + } + + @Test + public void size1() { + HAssert.assertEquals(1, new Path<>("a").size()); + } + + @Test + public void size2() { + HAssert.assertEquals(2, new Path<>("a", "b").size()); + } + + @Test + public void startsWithA() { + HAssert.assertTrue(new Path<>("a").startsWith(new Path<>("a"))); + } + + @Test + public void startsWithAB() { + HAssert.assertTrue(new Path<>("a", "b").startsWith(new Path<>("a"))); + } + + @Test + public void startsWithAC() { + HAssert.assertFalse(new Path<>("a", "b").startsWith(new Path<>("a", "c"))); + } + + @Test + public void startsWithB() { + HAssert.assertFalse(new Path<>("a").startsWith(new Path<>("b"))); + } + + @Test + public void startsWithShort() { + HAssert.assertFalse(new Path<>("a").startsWith(new Path<>("a", "b"))); + } } From dc8558df0e8c5a1179490a10a3ba20746b7f046f Mon Sep 17 00:00:00 2001 From: Greg Gibeling Date: Wed, 25 Sep 2024 16:21:22 -0700 Subject: [PATCH 2/2] G2-1646 More standard path operations --- .../g2forge/alexandria/path/path/IPath.java | 23 ++++++++--- .../g2forge/alexandria/path/path/Path.java | 9 +++++ .../path/path/format/IPathFormat.java | 7 +++- .../path/path/format/IStandardPathFormat.java | 12 +++--- .../path/path/format/IStringPathFormat.java | 15 +++++++ .../path/path/format/OSPathFormat.java | 15 ++++--- .../alexandria/path/path/TestPath.java | 40 +++++++++++++++++++ 7 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IStringPathFormat.java diff --git a/ax-path/src/main/java/com/g2forge/alexandria/path/path/IPath.java b/ax-path/src/main/java/com/g2forge/alexandria/path/path/IPath.java index c254c61c..0173ca7a 100644 --- a/ax-path/src/main/java/com/g2forge/alexandria/path/path/IPath.java +++ b/ax-path/src/main/java/com/g2forge/alexandria/path/path/IPath.java @@ -6,7 +6,6 @@ import com.g2forge.alexandria.collection.ICollection; import com.g2forge.alexandria.java.adt.compare.CollectionComparator; -import com.g2forge.alexandria.java.core.error.IllegalOperationException; import com.g2forge.alexandria.java.core.helpers.HCollection; public interface IPath { @@ -32,18 +31,27 @@ public default boolean endsWith(IPath other) { return thisEnding.equals(thatList); } + public default boolean endsWith(T component) { + if (isEmpty()) return false; + return getLast().equals(component); + } + public default T getComponent(int index) { return HCollection.get(getComponents().toCollection(), index); } public ICollection getComponents(); - public default IPath getParent() { - if (isEmpty()) throw new IllegalOperationException(); - final List list = HCollection.asList(getComponents().toCollection()); - return new Path<>(list.subList(0, list.size() - 1)); + public default T getFirst() { + return HCollection.getFirst(getComponents().toCollection()); + } + + public default T getLast() { + return HCollection.getLast(getComponents().toCollection()); } + public IPath getParent(); + public default boolean isEmpty() { return getComponents().isEmpty(); } @@ -66,4 +74,9 @@ public default boolean startsWith(IPath other) { final List beginning = HCollection.asList(getComponents().toCollection()).subList(0, thatSize); return beginning.equals(other.getComponents().toCollection()); } + + public default boolean startsWith(T component) { + if (isEmpty()) return false; + return getFirst().equals(component); + } } diff --git a/ax-path/src/main/java/com/g2forge/alexandria/path/path/Path.java b/ax-path/src/main/java/com/g2forge/alexandria/path/path/Path.java index 29da997f..aaf61402 100644 --- a/ax-path/src/main/java/com/g2forge/alexandria/path/path/Path.java +++ b/ax-path/src/main/java/com/g2forge/alexandria/path/path/Path.java @@ -1,10 +1,12 @@ package com.g2forge.alexandria.path.path; import java.util.Collection; +import java.util.List; import com.g2forge.alexandria.collection.CollectionCollection; import com.g2forge.alexandria.collection.EmptyCollection; import com.g2forge.alexandria.collection.ICollection; +import com.g2forge.alexandria.java.core.error.IllegalOperationException; import com.g2forge.alexandria.java.core.helpers.HCollection; import lombok.Builder; @@ -33,6 +35,13 @@ public Path(T... components) { this(components.length < 1 ? EmptyCollection.create() : new CollectionCollection<>(components)); } + @Override + public IPath getParent() { + if (isEmpty()) throw new IllegalOperationException(); + final List list = HCollection.asList(getComponents().toCollection()); + return new Path<>(list.subList(0, list.size() - 1)); + } + @Override public IPath resolve(IPath subpath) { if (isEmpty()) return subpath; diff --git a/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IPathFormat.java b/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IPathFormat.java index 315b758a..b50c1677 100644 --- a/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IPathFormat.java +++ b/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IPathFormat.java @@ -1,11 +1,14 @@ package com.g2forge.alexandria.path.path.format; +import com.g2forge.alexandria.collection.ICollection; import com.g2forge.alexandria.path.path.IPath; -public interface IPathFormat { +public interface IPathFormat> { public T toComponent(String component); - public IPath toPath(String path); + public P toPath(ICollection components); + + public P toPath(String path); public String toString(IPath path); diff --git a/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IStandardPathFormat.java b/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IStandardPathFormat.java index 1d1e9184..08c3d526 100644 --- a/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IStandardPathFormat.java +++ b/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IStandardPathFormat.java @@ -1,19 +1,21 @@ package com.g2forge.alexandria.path.path.format; +import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.g2forge.alexandria.collection.CollectionCollection; import com.g2forge.alexandria.path.path.IPath; -import com.g2forge.alexandria.path.path.Path; -public interface IStandardPathFormat extends IPathFormat { +public interface IStandardPathFormat> extends IPathFormat { public String getSeparator(); @Override - public default IPath toPath(String path) { - final String[] components = path.split(Pattern.quote(getSeparator())); - return new Path<>(Stream.of(components).map(this::toComponent).collect(Collectors.toList())); + public default P toPath(String path) { + final String[] componentsArray = path.split(Pattern.quote(getSeparator())); + final List componentsList = Stream.of(componentsArray).map(this::toComponent).collect(Collectors.toList()); + return toPath(new CollectionCollection<>(componentsList)); } @Override diff --git a/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IStringPathFormat.java b/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IStringPathFormat.java new file mode 100644 index 00000000..b1f59ee7 --- /dev/null +++ b/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/IStringPathFormat.java @@ -0,0 +1,15 @@ +package com.g2forge.alexandria.path.path.format; + +import com.g2forge.alexandria.path.path.IPath; + +public interface IStringPathFormat

> extends IStandardPathFormat { + @Override + public default String toComponent(String component) { + return component; + } + + @Override + public default String toString(String component) { + return component; + } +} diff --git a/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/OSPathFormat.java b/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/OSPathFormat.java index 8c99ad5c..c71f76c7 100644 --- a/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/OSPathFormat.java +++ b/ax-path/src/main/java/com/g2forge/alexandria/path/path/format/OSPathFormat.java @@ -1,23 +1,22 @@ package com.g2forge.alexandria.path.path.format; +import com.g2forge.alexandria.collection.ICollection; +import com.g2forge.alexandria.path.path.IPath; +import com.g2forge.alexandria.path.path.Path; + import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor -public enum OSPathFormat implements IStandardPathFormat { +public enum OSPathFormat implements IStringPathFormat> { Microsoft("\\"), POSIX("/"); protected final String separator; @Override - public String toComponent(String component) { - return component; - } - - @Override - public String toString(String component) { - return component; + public IPath toPath(ICollection components) { + return new Path<>(components); } } diff --git a/ax-path/src/test/java/com/g2forge/alexandria/path/path/TestPath.java b/ax-path/src/test/java/com/g2forge/alexandria/path/path/TestPath.java index 4bfee5f3..732e72dc 100644 --- a/ax-path/src/test/java/com/g2forge/alexandria/path/path/TestPath.java +++ b/ax-path/src/test/java/com/g2forge/alexandria/path/path/TestPath.java @@ -23,16 +23,36 @@ public void endsWithAC() { HAssert.assertFalse(new Path<>("a", "b").endsWith(new Path<>("a", "c"))); } + @Test + public void endsWithAComponent() { + HAssert.assertTrue(new Path<>("a").endsWith("a")); + } + @Test public void endsWithB() { HAssert.assertFalse(new Path<>("a").endsWith(new Path<>("b"))); } + @Test + public void endsWithBComponent() { + HAssert.assertFalse(new Path<>("a").endsWith("b")); + } + + @Test + public void endsWithCComponent() { + HAssert.assertTrue(new Path<>("a", "b", "c").endsWith("c")); + } + @Test public void endsWithShort() { HAssert.assertFalse(new Path<>("a").endsWith(new Path<>("a", "b"))); } + @Test + public void endsWithShortComponent() { + HAssert.assertFalse(Path.createEmpty().endsWith("a")); + } + @Test public void getParent() { HAssert.assertEquals(new Path<>("a"), new Path<>("a", "b").getParent()); @@ -121,13 +141,33 @@ public void startsWithAC() { HAssert.assertFalse(new Path<>("a", "b").startsWith(new Path<>("a", "c"))); } + @Test + public void startsWithAComponent() { + HAssert.assertTrue(new Path<>("a").startsWith("a")); + } + @Test public void startsWithB() { HAssert.assertFalse(new Path<>("a").startsWith(new Path<>("b"))); } + @Test + public void startsWithBComponent() { + HAssert.assertFalse(new Path<>("a").startsWith("b")); + } + + @Test + public void startsWithCComponent() { + HAssert.assertTrue(new Path<>("a", "b", "c").startsWith("a")); + } + @Test public void startsWithShort() { HAssert.assertFalse(new Path<>("a").startsWith(new Path<>("a", "b"))); } + + @Test + public void startsWithShortComponent() { + HAssert.assertFalse(Path.createEmpty().startsWith("a")); + } }