summaryrefslogtreecommitdiffstats
path: root/cps-path-parser
diff options
context:
space:
mode:
Diffstat (limited to 'cps-path-parser')
-rw-r--r--cps-path-parser/pom.xml25
-rw-r--r--cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g44
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java16
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java3
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java36
-rw-r--r--cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy90
-rw-r--r--cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/performance/CpsPathUtilPerfTest.groovy43
7 files changed, 210 insertions, 7 deletions
diff --git a/cps-path-parser/pom.xml b/cps-path-parser/pom.xml
index d9c150881d..d0da105aae 100644
--- a/cps-path-parser/pom.xml
+++ b/cps-path-parser/pom.xml
@@ -89,4 +89,29 @@
</dependency>
</dependencies>
+ <profiles>
+ <profile>
+ <id>default</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>%regex[.*PerfTest.*]</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>include-performance</id>
+ </profile>
+ </profiles>
+
</project>
diff --git a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
index 40ad410a0d..db09b3c532 100644
--- a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
+++ b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
@@ -28,7 +28,9 @@ ancestorPath : yangElement ( SLASH yangElement)* ;
textFunctionCondition : SLASH leafName OB KW_TEXT_FUNCTION EQ StringLiteral CB ;
-prefix : ( SLASH yangElement)* SLASH containerName ;
+parent : ( SLASH yangElement)* ;
+
+prefix : parent SLASH containerName ;
descendant : SLASH prefix ;
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
index 21f5173a98..3a9d70ebbc 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
@@ -22,7 +22,9 @@ package org.onap.cps.cpspath.parser;
import static org.onap.cps.cpspath.parser.CpsPathPrefixType.DESCENDANT;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.onap.cps.cpspath.parser.antlr4.CpsPathBaseListener;
import org.onap.cps.cpspath.parser.antlr4.CpsPathParser;
@@ -50,6 +52,8 @@ public class CpsPathBuilder extends CpsPathBaseListener {
boolean processingAncestorAxis = false;
+ private List<String> containerNames = new ArrayList<>();
+
@Override
public void exitInvalidPostFix(final CpsPathParser.InvalidPostFixContext ctx) {
throw new PathParsingException(ctx.getText());
@@ -61,6 +65,11 @@ public class CpsPathBuilder extends CpsPathBaseListener {
}
@Override
+ public void exitParent(final CpsPathParser.ParentContext ctx) {
+ cpsPathQuery.setNormalizedParentPath(normalizedXpathBuilder.toString());
+ }
+
+ @Override
public void exitIncorrectPrefix(final IncorrectPrefixContext ctx) {
throw new PathParsingException("CPS path can only start with one or two slashes (/)");
}
@@ -141,6 +150,7 @@ public class CpsPathBuilder extends CpsPathBaseListener {
CpsPathQuery build() {
cpsPathQuery.setNormalizedXpath(normalizedXpathBuilder.toString());
+ cpsPathQuery.setContainerNames(containerNames);
return cpsPathQuery;
}
@@ -150,10 +160,12 @@ public class CpsPathBuilder extends CpsPathBaseListener {
@Override
public void exitContainerName(final CpsPathParser.ContainerNameContext ctx) {
+ final String containerName = ctx.getText();
normalizedXpathBuilder.append("/")
- .append(ctx.getText());
+ .append(containerName);
+ containerNames.add(containerName);
if (processingAncestorAxis) {
- normalizedAncestorPathBuilder.append("/").append(ctx.getText());
+ normalizedAncestorPathBuilder.append("/").append(containerName);
}
}
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
index 53490f3a2d..c9df8df904 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
@@ -22,6 +22,7 @@ package org.onap.cps.cpspath.parser;
import static org.onap.cps.cpspath.parser.CpsPathPrefixType.ABSOLUTE;
+import java.util.List;
import java.util.Map;
import lombok.AccessLevel;
import lombok.Getter;
@@ -32,7 +33,9 @@ import lombok.Setter;
public class CpsPathQuery {
private String xpathPrefix;
+ private String normalizedParentPath;
private String normalizedXpath;
+ private List<String> containerNames;
private CpsPathPrefixType cpsPathPrefixType = ABSOLUTE;
private String descendantName;
private Map<String, Object> leavesData;
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java
index 97d7d1d760..60f0e2efcd 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java
@@ -20,6 +20,9 @@
package org.onap.cps.cpspath.parser;
+import static org.onap.cps.cpspath.parser.CpsPathPrefixType.ABSOLUTE;
+
+import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -45,8 +48,34 @@ public class CpsPathUtil {
* @return a normalized xpath String.
*/
public static String getNormalizedXpath(final String xpathSource) {
- final CpsPathBuilder cpsPathBuilder = getCpsPathBuilder(xpathSource);
- return cpsPathBuilder.build().getNormalizedXpath();
+ return getCpsPathBuilder(xpathSource).build().getNormalizedXpath();
+ }
+
+ /**
+ * Returns the parent xpath.
+ *
+ * @param xpathSource xpath
+ * @return the parent xpath String.
+ */
+ public static String getNormalizedParentXpath(final String xpathSource) {
+ return getCpsPathBuilder(xpathSource).build().getNormalizedParentPath();
+ }
+
+ public static String[] getXpathNodeIdSequence(final String xpathSource) {
+ final List<String> containerNames = getCpsPathBuilder(xpathSource).build().getContainerNames();
+ return containerNames.toArray(new String[containerNames.size()]);
+ }
+
+
+ /**
+ * Returns boolean indicating xpath is an absolute path to a list element.
+ *
+ * @param xpathSource xpath
+ * @return true if xpath is an absolute path to a list element
+ */
+ public static boolean isPathToListElement(final String xpathSource) {
+ final CpsPathQuery cpsPathQuery = getCpsPathBuilder(xpathSource).build();
+ return cpsPathQuery.getCpsPathPrefixType() == ABSOLUTE && cpsPathQuery.hasLeafConditions();
}
/**
@@ -57,8 +86,7 @@ public class CpsPathUtil {
*/
public static CpsPathQuery getCpsPathQuery(final String cpsPathSource) {
- final CpsPathBuilder cpsPathBuilder = getCpsPathBuilder(cpsPathSource);
- return cpsPathBuilder.build();
+ return getCpsPathBuilder(cpsPathSource).build();
}
private static CpsPathBuilder getCpsPathBuilder(final String cpsPathSource) {
diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy
new file mode 100644
index 0000000000..d62f337b75
--- /dev/null
+++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy
@@ -0,0 +1,90 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.cpspath.parser
+
+import spock.lang.Specification
+
+class CpsPathUtilSpec extends Specification {
+
+ def 'Normalized xpaths for list index values using #scenario'() {
+ when: 'xpath with #scenario is parsed'
+ def result = CpsPathUtil.getNormalizedXpath(xpath)
+ then: 'normalized path uses single quotes for leave values'
+ assert result == "/parent/child[@common-leaf-name='123']"
+ where: 'the following xpaths are used'
+ scenario | xpath
+ 'no quotes' | '/parent/child[@common-leaf-name=123]'
+ 'double quotes' | '/parent/child[@common-leaf-name="123"]'
+ 'single quotes' | "/parent/child[@common-leaf-name='123']"
+ }
+
+ def 'Normalized parent xpaths'() {
+ when: 'a given xpath with #scenario is parsed'
+ def result = CpsPathUtil.getNormalizedParentXpath(xpath)
+ then: 'the result is the expected parent path'
+ assert result == expectedParentPath
+ where: 'the following xpaths are used'
+ scenario | xpath || expectedParentPath
+ 'no child' | '/parent' || ''
+ 'child and parent' | '/parent/child' || '/parent'
+ 'grand child' | '/parent/child/grandChild' || '/parent/child'
+ 'parent & top is list element' | '/parent[@id=1]/child' || "/parent[@id='1']"
+ 'parent is list element' | '/parent/child[@id=1]/grandChild' || "/parent/child[@id='1']"
+ 'parent is list element with /' | "/parent/child[@id='a/b']/grandChild" || "/parent/child[@id='a/b']"
+ 'parent is list element with [' | "/parent/child[@id='a[b']/grandChild" || "/parent/child[@id='a[b']"
+ 'parent is list element using "' | '/parent/child[@id="x"]/grandChild' || "/parent/child[@id='x']"
+ }
+
+ def 'Get node ID sequence for given xpath'() {
+ when: 'a given xpath with #scenario is parsed'
+ def result = CpsPathUtil.getXpathNodeIdSequence(xpath)
+ then: 'the result is the expected node ID sequence'
+ assert result == expectedNodeIdSequence
+ where: 'the following xpaths are used'
+ scenario | xpath || expectedNodeIdSequence
+ 'no child' | '/parent' || ["parent"]
+ 'child and parent' | '/parent/child' || ["parent","child"]
+ 'grand child' | '/parent/child/grandChild' || ["parent","child","grandChild"]
+ 'parent & top is list element' | '/parent[@id=1]/child' || ["parent","child"]
+ 'parent is list element' | '/parent/child[@id=1]/grandChild' || ["parent","child","grandChild"]
+ 'parent is list element with /' | "/parent/child[@id='a/b']/grandChild" || ["parent","child","grandChild"]
+ 'parent is list element with [' | "/parent/child[@id='a[b']/grandChild" || ["parent","child","grandChild"]
+ }
+
+ def 'Recognizing (absolute) xpaths to List elements'() {
+ expect: 'check for list returns the correct values'
+ assert CpsPathUtil.isPathToListElement(xpath) == expectList
+ where: 'the following xpaths are used'
+ xpath || expectList
+ '/parent[@id=1]' || true
+ '/parent[@id=1]/child' || false
+ '/parent/child[@id=1]' || true
+ '//child[@id=1]' || false
+ }
+
+ def 'Parsing Exception'() {
+ when: 'a invalid xpath is parsed'
+ CpsPathUtil.getNormalizedXpath('///')
+ then: 'a path parsing exception is thrown'
+ thrown(PathParsingException)
+ }
+
+}
diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/performance/CpsPathUtilPerfTest.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/performance/CpsPathUtilPerfTest.groovy
new file mode 100644
index 0000000000..2ba20c1c5f
--- /dev/null
+++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/performance/CpsPathUtilPerfTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.cpspath.parser.performance
+
+import org.onap.cps.cpspath.parser.CpsPathUtil
+import org.springframework.util.StopWatch
+import spock.lang.Specification
+
+class CpsPathUtilPerfTest extends Specification {
+
+ def 'CPS Path Processing Performance Test.'() {
+ when: '20,000 paths are processed'
+ def stopWatch = new StopWatch()
+ stopWatch.start()
+ (1..10000).each {
+ CpsPathUtil.getNormalizedXpath('/long/path/to/see/if/it/adds/paring/time/significantly/parent/child[@common-leaf-name="123"]')
+ CpsPathUtil.getNormalizedXpath('//child[@other-leaf=1]/leaf-name[text()="search"]/ancestor::parent')
+ }
+ stopWatch.stop()
+ then: 'it takes less then 1,000 milliseconds'
+ // In CI this actually takes about 0.3-0.5 sec which is approx. 50+ parser executions per millisecond!
+ assert stopWatch.getTotalTimeMillis() < 1000
+ }
+
+}