Merge "Apostrophe handling in CpsPathParser"
[cps.git] / cps-path-parser / src / main / java / org / onap / cps / cpspath / parser / CpsPathBuilder.java
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2023 Nordix Foundation
4  *  Modifications Copyright (C) 2023 TechMahindra Ltd
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 package org.onap.cps.cpspath.parser;
23
24 import static org.onap.cps.cpspath.parser.CpsPathPrefixType.DESCENDANT;
25
26 import java.util.ArrayList;
27 import java.util.List;
28 import org.onap.cps.cpspath.parser.antlr4.CpsPathBaseListener;
29 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser;
30 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.AncestorAxisContext;
31 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.DescendantContext;
32 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.IncorrectPrefixContext;
33 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.LeafConditionContext;
34 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.MultipleLeafConditionsContext;
35 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.PrefixContext;
36 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.TextFunctionConditionContext;
37
38 public class CpsPathBuilder extends CpsPathBaseListener {
39
40     private static final String OPEN_BRACKET = "[";
41
42     private static final String CLOSE_BRACKET = "]";
43
44     private final CpsPathQuery cpsPathQuery = new CpsPathQuery();
45
46     private final List<CpsPathQuery.DataLeaf> leavesData = new ArrayList<>();
47
48     private final StringBuilder normalizedXpathBuilder = new StringBuilder();
49
50     private final StringBuilder normalizedAncestorPathBuilder = new StringBuilder();
51
52     private boolean processingAncestorAxis = false;
53
54     private final List<String> containerNames = new ArrayList<>();
55
56     private final List<String> booleanOperators = new ArrayList<>();
57
58     private final List<String> comparativeOperators = new ArrayList<>();
59
60     @Override
61     public void exitInvalidPostFix(final CpsPathParser.InvalidPostFixContext ctx) {
62         throw new PathParsingException(ctx.getText());
63     }
64
65     @Override
66     public void exitPrefix(final PrefixContext ctx) {
67         cpsPathQuery.setXpathPrefix(normalizedXpathBuilder.toString());
68     }
69
70     @Override
71     public void exitParent(final CpsPathParser.ParentContext ctx) {
72         cpsPathQuery.setNormalizedParentPath(normalizedXpathBuilder.toString());
73     }
74
75     @Override
76     public void exitIncorrectPrefix(final IncorrectPrefixContext ctx) {
77         throw new PathParsingException("CPS path can only start with one or two slashes (/)");
78     }
79
80     @Override
81     public void exitLeafCondition(final LeafConditionContext ctx) {
82         final Object comparisonValue;
83         if (ctx.IntegerLiteral() != null) {
84             comparisonValue = Integer.valueOf(ctx.IntegerLiteral().getText());
85         } else if (ctx.StringLiteral() != null) {
86             comparisonValue = unwrapQuotedString(ctx.StringLiteral().getText());
87         } else {
88             throw new PathParsingException("Unsupported comparison value encountered in expression" + ctx.getText());
89         }
90         leafContext(ctx.leafName(), comparisonValue);
91     }
92
93     @Override
94     public void exitBooleanOperators(final CpsPathParser.BooleanOperatorsContext ctx) {
95         booleanOperators.add(ctx.getText());
96     }
97
98     @Override
99     public void exitComparativeOperators(final CpsPathParser.ComparativeOperatorsContext ctx) {
100         comparativeOperators.add(ctx.getText());
101     }
102
103     @Override
104     public void exitDescendant(final DescendantContext ctx) {
105         cpsPathQuery.setCpsPathPrefixType(DESCENDANT);
106         cpsPathQuery.setDescendantName(normalizedXpathBuilder.substring(1));
107         normalizedXpathBuilder.insert(0, "/");
108     }
109
110     @Override
111     public void enterMultipleLeafConditions(final MultipleLeafConditionsContext ctx)  {
112         normalizedXpathBuilder.append(OPEN_BRACKET);
113         leavesData.clear();
114         booleanOperators.clear();
115         comparativeOperators.clear();
116     }
117
118     @Override
119     public void exitMultipleLeafConditions(final MultipleLeafConditionsContext ctx) {
120         normalizedXpathBuilder.append(CLOSE_BRACKET);
121         cpsPathQuery.setLeavesData(leavesData);
122     }
123
124     @Override
125     public void enterAncestorAxis(final AncestorAxisContext ctx) {
126         processingAncestorAxis = true;
127     }
128
129     @Override
130     public void exitAncestorAxis(final AncestorAxisContext ctx) {
131         cpsPathQuery.setAncestorSchemaNodeIdentifier(normalizedAncestorPathBuilder.substring(1));
132         processingAncestorAxis = false;
133     }
134
135     @Override
136     public void exitTextFunctionCondition(final TextFunctionConditionContext ctx) {
137         cpsPathQuery.setTextFunctionConditionLeafName(ctx.leafName().getText());
138         cpsPathQuery.setTextFunctionConditionValue(unwrapQuotedString(ctx.StringLiteral().getText()));
139     }
140
141     @Override
142     public void exitContainsFunctionCondition(final CpsPathParser.ContainsFunctionConditionContext ctx) {
143         cpsPathQuery.setContainsFunctionConditionLeafName(ctx.leafName().getText());
144         cpsPathQuery.setContainsFunctionConditionValue(unwrapQuotedString(ctx.StringLiteral().getText()));
145     }
146
147     @Override
148     public void enterListElementRef(final CpsPathParser.ListElementRefContext ctx) {
149         normalizedXpathBuilder.append(OPEN_BRACKET);
150         if (processingAncestorAxis) {
151             normalizedAncestorPathBuilder.append(OPEN_BRACKET);
152         }
153     }
154
155     @Override
156     public void exitListElementRef(final CpsPathParser.ListElementRefContext ctx) {
157         normalizedXpathBuilder.append(CLOSE_BRACKET);
158         if (processingAncestorAxis) {
159             normalizedAncestorPathBuilder.append(CLOSE_BRACKET);
160         }
161     }
162
163     CpsPathQuery build() {
164         cpsPathQuery.setNormalizedXpath(normalizedXpathBuilder.toString());
165         cpsPathQuery.setContainerNames(containerNames);
166         cpsPathQuery.setBooleanOperators(booleanOperators);
167         cpsPathQuery.setComparativeOperators(comparativeOperators);
168         return cpsPathQuery;
169     }
170
171     @Override
172     public void exitContainerName(final CpsPathParser.ContainerNameContext ctx) {
173         final String containerName = ctx.getText();
174         normalizedXpathBuilder.append("/")
175                 .append(containerName);
176         containerNames.add(containerName);
177         if (processingAncestorAxis) {
178             normalizedAncestorPathBuilder.append("/").append(containerName);
179         }
180     }
181
182     private void leafContext(final CpsPathParser.LeafNameContext ctx, final Object comparisonValue) {
183         leavesData.add(new CpsPathQuery.DataLeaf(ctx.getText(), comparisonValue));
184         appendCondition(normalizedXpathBuilder, ctx.getText(), comparisonValue);
185         if (processingAncestorAxis) {
186             appendCondition(normalizedAncestorPathBuilder, ctx.getText(), comparisonValue);
187         }
188     }
189
190     private void appendCondition(final StringBuilder currentNormalizedPathBuilder, final String name,
191                                  final Object value) {
192         final char lastCharacter = currentNormalizedPathBuilder.charAt(currentNormalizedPathBuilder.length() - 1);
193         final boolean isStartOfExpression = lastCharacter == '[';
194         if (!isStartOfExpression) {
195             currentNormalizedPathBuilder.append(" ").append(getLastElement(booleanOperators)).append(" ");
196         }
197         currentNormalizedPathBuilder.append("@")
198                                     .append(name)
199                                     .append(getLastElement(comparativeOperators))
200                                     .append("'")
201                                     .append(value.toString().replace("'", "''"))
202                                     .append("'");
203     }
204
205     private static String getLastElement(final List<String> listOfStrings) {
206         return listOfStrings.get(listOfStrings.size() - 1);
207     }
208
209     private static String unwrapQuotedString(final String wrappedString) {
210         final boolean wasWrappedInSingleQuote = wrappedString.startsWith("'");
211         final String value = stripFirstAndLastCharacter(wrappedString);
212         if (wasWrappedInSingleQuote) {
213             return value.replace("''", "'");
214         } else {
215             return value.replace("\"\"", "\"");
216         }
217     }
218
219     private static String stripFirstAndLastCharacter(final String wrappedString) {
220         return wrappedString.substring(1, wrappedString.length() - 1);
221     }
222 }