Skip to content

Commit

Permalink
CAY-2833 Unify code related to the Cayenne model paths processing
Browse files Browse the repository at this point in the history
 - performance optimizations
  • Loading branch information
stariy95 committed Jan 23, 2024
1 parent cf80017 commit 0765b65
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 33 deletions.
26 changes: 10 additions & 16 deletions cayenne/src/main/java/org/apache/cayenne/exp/path/CayennePath.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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());
}

Expand Down
66 changes: 50 additions & 16 deletions cayenne/src/main/java/org/apache/cayenne/exp/path/PathParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<count; i++) {
String segment = segments[i];
if(segment.isEmpty()) {
if(i == 0) {
throw new IllegalArgumentException("Path can't start with a '.'");
} else if(i == count - 1) {
throw new IllegalArgumentException("Path can't end with a '.'");
} else {
throw new IllegalArgumentException("Path can't have two consecutive '.' chars");

/**
* NOTE: this method is optimized for internal use only and expects non-empty string
*
* @param path string to parse, must be not null
* @return array of path components or {@code null} if path is a simple segment
*
* @see CayennePath#of(String, int)
*/
static CayennePathSegment[] parseAllSegments(String path) {
int off = 0;
int i = 0;
int next;
CayennePathSegment[] result = null;
while ((next = path.indexOf('.', off)) != -1) {
if(off == next) {
throw new IllegalArgumentException("Illegal path expression");
}
if(result == null) {
result = new CayennePathSegment[INITIAL_SEGMENTS_COUNT];
}
result[i++] = parseSegment(path, off, next);
if(i == result.length) {
if(result.length > 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
};
}

Expand All @@ -50,27 +51,31 @@ public void size() {
assertEquals(1, paths[0].length());
assertEquals(2, paths[1].length());
assertEquals(3, paths[2].length());
assertEquals(4, paths[3].length());
}

@Test
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
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
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());
}

}
Original file line number Diff line number Diff line change
@@ -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]);
}

}

0 comments on commit 0765b65

Please sign in to comment.