diff --git a/cayenne/src/main/java/org/apache/cayenne/exp/path/CayennePath.java b/cayenne/src/main/java/org/apache/cayenne/exp/path/CayennePath.java index a519c39e75..b50375d526 100644 --- a/cayenne/src/main/java/org/apache/cayenne/exp/path/CayennePath.java +++ b/cayenne/src/main/java/org/apache/cayenne/exp/path/CayennePath.java @@ -118,27 +118,18 @@ static CayennePath of(String path, int modifier) { } } - // empty path - String[] components = PathParser.parseComponents(path); - if (components.length == 0) { - if(modifier == NO_MODIFIER) { - return EMPTY_PATH; - } else { - return new EmptyCayennePath(modifier); - } - } - + CayennePathSegment[] segments = PathParser.parseAllSegments(path); // fast case for a single segment path - if (components.length == 1) { - return new SingleSegmentCayennePath(PathParser.parseSegment(components[0]), modifier); + if (segments == null) { + return new SingleSegmentCayennePath(PathParser.parseSegment(path), modifier); } // general case for a path with a multiple segments - CayennePathSegment[] segments = new CayennePathSegment[components.length]; - for (int i = 0; i < components.length; i++) { - segments[i] = PathParser.parseSegment(components[i]); + int counter = 0; + while(counter < segments.length && segments[counter] != null) { + counter++; } - return new MultiSegmentCayennePath(new SegmentList(segments, 0, segments.length), modifier); + return new MultiSegmentCayennePath(new SegmentList(segments, 0, counter), modifier); } /** @@ -317,6 +308,9 @@ default CayennePath tail(int start) { * @return a new path ending at the end index */ default CayennePath head(int end) { + if(end == length()) { + return this; + } return of(segments().subList(0, end), modifier()); } diff --git a/cayenne/src/main/java/org/apache/cayenne/exp/path/PathParser.java b/cayenne/src/main/java/org/apache/cayenne/exp/path/PathParser.java index 434dab3402..5c087fa02f 100644 --- a/cayenne/src/main/java/org/apache/cayenne/exp/path/PathParser.java +++ b/cayenne/src/main/java/org/apache/cayenne/exp/path/PathParser.java @@ -24,33 +24,67 @@ */ class PathParser { - static String[] parseComponents(String path) { - String[] segments = path.split("\\."); - int count = segments.length; + private static final int INITIAL_SEGMENTS_COUNT = 4; + private static final int MAX_SEGMENTS_COUNT = 10000; - // validate segments - for(int i=0; i MAX_SEGMENTS_COUNT) { + // some sanity check + throw new IllegalArgumentException("Illegal path expression"); } + CayennePathSegment[] newList = new CayennePathSegment[result.length * 2]; + System.arraycopy(result, 0, newList, 0, result.length); + result = newList; } + off = next + 1; + } + if(off == 0) { + return null; } + if(off == path.length()) { + throw new IllegalArgumentException("Illegal path expression"); + } + // Add remaining segment + result[i] = parseSegment(path, off, path.length()); + return result; + } - return segments; + static CayennePathSegment parseSegment(String segment, int start, int end) { + if(segment.charAt(end - 1) == '+') { + return new CayennePathSegment(segment.substring(start, end - 1), CayennePath.OUTER_MODIFIER); + } else { + return new CayennePathSegment(segment.substring(start, end), CayennePath.NO_MODIFIER); + } } static CayennePathSegment parseSegment(String segment) { int modifier = CayennePath.NO_MODIFIER; if(segment.length() > 1 && segment.charAt(segment.length() - 1) == '+') { segment = segment.substring(0, segment.length() - 1); - modifier |= CayennePath.OUTER_MODIFIER; + modifier = CayennePath.OUTER_MODIFIER; } - return CayennePath.segmentOf(segment, modifier); + return new CayennePathSegment(segment, modifier); } } diff --git a/cayenne/src/test/java/org/apache/cayenne/exp/CayennePathTest.java b/cayenne/src/test/java/org/apache/cayenne/exp/CayennePathTest.java index d5df1d2cd4..3dd73abbee 100644 --- a/cayenne/src/test/java/org/apache/cayenne/exp/CayennePathTest.java +++ b/cayenne/src/test/java/org/apache/cayenne/exp/CayennePathTest.java @@ -34,7 +34,8 @@ public void setUp() { paths = new CayennePath[]{ CayennePath.of("a"), CayennePath.of("a+.bcd"), - CayennePath.of("a.bc.defg") + CayennePath.of("a.bc.defg"), + CayennePath.of("a.bc.defg.edad") }; } @@ -50,6 +51,7 @@ public void size() { assertEquals(1, paths[0].length()); assertEquals(2, paths[1].length()); assertEquals(3, paths[2].length()); + assertEquals(4, paths[3].length()); } @Test @@ -57,6 +59,7 @@ public void getLast() { assertEquals("a", paths[0].last().toString()); assertEquals("bcd", paths[1].last().toString()); assertEquals("defg", paths[2].last().toString()); + assertEquals("edad", paths[3].last().toString()); } @Test @@ -64,6 +67,7 @@ public void getParent() { assertEquals("", paths[0].parent().toString()); assertEquals("a+", paths[1].parent().toString()); assertEquals("a.bc", paths[2].parent().toString()); + assertEquals("a.bc.defg", paths[3].parent().toString()); } @Test @@ -71,6 +75,7 @@ public void getPath() { assertEquals("a", paths[0].toString()); assertEquals("a+.bcd", paths[1].toString()); assertEquals("a.bc.defg", paths[2].toString()); + assertEquals("a.bc.defg.edad", paths[3].toString()); } } \ No newline at end of file diff --git a/cayenne/src/test/java/org/apache/cayenne/exp/path/PathParserTest.java b/cayenne/src/test/java/org/apache/cayenne/exp/path/PathParserTest.java new file mode 100644 index 0000000000..67b580b565 --- /dev/null +++ b/cayenne/src/test/java/org/apache/cayenne/exp/path/PathParserTest.java @@ -0,0 +1,92 @@ +package org.apache.cayenne.exp.path; + + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class PathParserTest { + + @Test + public void testParseSingle() { + assertNull(PathParser.parseAllSegments("")); + assertNull(PathParser.parseAllSegments("bd:test+")); + assertNull(PathParser.parseAllSegments("test")); + } + + @Test + public void testTwoSegmentsSimple() { + CayennePathSegment[] segments = PathParser.parseAllSegments("abc.bed"); + assertNotNull(segments); + assertEquals(4, segments.length); + assertEquals("abc", segments[0].value()); + assertFalse(segments[0].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("bed", segments[1].value()); + assertFalse(segments[1].hasModifier(CayennePath.OUTER_MODIFIER)); + assertNull(segments[2]); + assertNull(segments[3]); + } + + @Test + public void testTwoSegmentsOuter() { + CayennePathSegment[] segments = PathParser.parseAllSegments("a+.b+"); + assertNotNull(segments); + assertEquals(4, segments.length); + assertEquals("a", segments[0].value()); + assertTrue(segments[0].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("b", segments[1].value()); + assertTrue(segments[1].hasModifier(CayennePath.OUTER_MODIFIER)); + assertNull(segments[2]); + assertNull(segments[3]); + } + + @Test + public void testThreeSegments() { + CayennePathSegment[] segments = PathParser.parseAllSegments("a.b.c"); + assertNotNull(segments); + assertEquals(4, segments.length); + assertEquals("a", segments[0].value()); + assertFalse(segments[0].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("b", segments[1].value()); + assertFalse(segments[1].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("c", segments[2].value()); + assertFalse(segments[2].hasModifier(CayennePath.OUTER_MODIFIER)); + assertNull(segments[3]); + } + + @Test + public void testFourSegments() { + CayennePathSegment[] segments = PathParser.parseAllSegments("a.b+.c+.d"); + assertNotNull(segments); + assertEquals(4, segments.length); + assertEquals("a", segments[0].value()); + assertFalse(segments[0].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("b", segments[1].value()); + assertTrue(segments[1].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("c", segments[2].value()); + assertTrue(segments[2].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("d", segments[3].value()); + assertFalse(segments[3].hasModifier(CayennePath.OUTER_MODIFIER)); + } + + @Test + public void testFiveSegments() { + CayennePathSegment[] segments = PathParser.parseAllSegments("a.b+.c+.d.e"); + assertNotNull(segments); + assertEquals(8, segments.length); + assertEquals("a", segments[0].value()); + assertFalse(segments[0].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("b", segments[1].value()); + assertTrue(segments[1].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("c", segments[2].value()); + assertTrue(segments[2].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("d", segments[3].value()); + assertFalse(segments[3].hasModifier(CayennePath.OUTER_MODIFIER)); + assertEquals("e", segments[4].value()); + assertFalse(segments[4].hasModifier(CayennePath.OUTER_MODIFIER)); + assertNull(segments[5]); + assertNull(segments[6]); + assertNull(segments[7]); + } + +} \ No newline at end of file