/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package org.apache.nifi.record.path.paths; import static org.apache.nifi.record.path.RecordPathParser.ARRAY_INDEX; import static org.apache.nifi.record.path.RecordPathParser.CHILD_REFERENCE; import static org.apache.nifi.record.path.RecordPathParser.CURRENT_FIELD; import static org.apache.nifi.record.path.RecordPathParser.DESCENDANT_REFERENCE; import static org.apache.nifi.record.path.RecordPathParser.EQUAL; import static org.apache.nifi.record.path.RecordPathParser.FIELD_NAME; import static org.apache.nifi.record.path.RecordPathParser.GREATER_THAN; import static org.apache.nifi.record.path.RecordPathParser.GREATER_THAN_EQUAL; import static org.apache.nifi.record.path.RecordPathParser.LESS_THAN; import static org.apache.nifi.record.path.RecordPathParser.LESS_THAN_EQUAL; import static org.apache.nifi.record.path.RecordPathParser.MAP_KEY; import static org.apache.nifi.record.path.RecordPathParser.NOT_EQUAL; import static org.apache.nifi.record.path.RecordPathParser.NUMBER; import static org.apache.nifi.record.path.RecordPathParser.NUMBER_LIST; import static org.apache.nifi.record.path.RecordPathParser.NUMBER_RANGE; import static org.apache.nifi.record.path.RecordPathParser.PARENT_REFERENCE; import static org.apache.nifi.record.path.RecordPathParser.PATH; import static org.apache.nifi.record.path.RecordPathParser.PREDICATE; import static org.apache.nifi.record.path.RecordPathParser.RELATIVE_PATH; import static org.apache.nifi.record.path.RecordPathParser.ROOT_REFERENCE; import static org.apache.nifi.record.path.RecordPathParser.STRING_LIST; import static org.apache.nifi.record.path.RecordPathParser.STRING_LITERAL; import static org.apache.nifi.record.path.RecordPathParser.WILDCARD; import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; import org.antlr.runtime.tree.Tree; import org.apache.nifi.record.path.NumericRange; import org.apache.nifi.record.path.exception.RecordPathException; import org.apache.nifi.record.path.filter.EqualsFilter; import org.apache.nifi.record.path.filter.GreaterThanFilter; import org.apache.nifi.record.path.filter.GreaterThanOrEqualFilter; import org.apache.nifi.record.path.filter.LessThanFilter; import org.apache.nifi.record.path.filter.LessThanOrEqualFilter; import org.apache.nifi.record.path.filter.NotEqualsFilter; import org.apache.nifi.record.path.filter.RecordPathFilter; public class RecordPathCompiler { public static RecordPathSegment compile(final Tree pathTree, final RecordPathSegment root, final boolean absolute) { RecordPathSegment parent = root; for (int i = 0; i < pathTree.getChildCount(); i++) { final Tree child = pathTree.getChild(i); parent = RecordPathCompiler.buildPath(child, parent, absolute); } return parent; } public static RecordPathSegment buildPath(final Tree tree, final RecordPathSegment parent, final boolean absolute) { switch (tree.getType()) { case ROOT_REFERENCE: { return new RootPath(); } case CHILD_REFERENCE: { final Tree childTree = tree.getChild(0); final int childTreeType = childTree.getType(); if (childTreeType == FIELD_NAME) { final String childName = childTree.getChild(0).getText(); return new ChildFieldPath(childName, parent, absolute); } else if (childTreeType == WILDCARD) { return new WildcardChildPath(parent, absolute); } else { throw new RecordPathException("Expected field name following '/' Token but found " + childTree); } } case ARRAY_INDEX: { final Tree indexListTree = tree.getChild(0); if (indexListTree.getType() == NUMBER_LIST) { if (indexListTree.getChildCount() == 1 && indexListTree.getChild(0).getType() == NUMBER) { final Tree indexTree = indexListTree.getChild(0); final int index = Integer.parseInt(indexTree.getText()); return new ArrayIndexPath(index, parent, absolute); } final List<NumericRange> indexList = new ArrayList<>(); for (int i = 0; i < indexListTree.getChildCount(); i++) { final Tree indexTree = indexListTree.getChild(i); if (indexTree.getType() == NUMBER) { final int index = Integer.valueOf(indexTree.getText()); indexList.add(new NumericRange(index, index)); } else if (indexTree.getType() == NUMBER_RANGE) { final int min = Integer.valueOf(indexTree.getChild(0).getText()); final int max = Integer.valueOf(indexTree.getChild(1).getText()); indexList.add(new NumericRange(min, max)); } else { throw new RecordPathException("Expected Number or Range following '[' Token but found " + indexTree); } } return new MultiArrayIndexPath(indexList, parent, absolute); } else { throw new RecordPathException("Expected Number or Range following '[' Token but found " + indexListTree); } } case MAP_KEY: { final Tree keyTree = tree.getChild(0); if (keyTree.getType() == STRING_LIST) { if (keyTree.getChildCount() == 1) { return new SingularMapKeyPath(keyTree.getChild(0).getText(), parent, absolute); } final List<String> keys = new ArrayList<>(keyTree.getChildCount()); for (int i = 0; i < keyTree.getChildCount(); i++) { keys.add(keyTree.getChild(i).getText()); } return new MultiMapKeyPath(keys, parent, absolute); } else { throw new RecordPathException("Expected Map Key following '[' Token but found " + keyTree); } } case WILDCARD: { return new WildcardIndexPath(parent, absolute); } case DESCENDANT_REFERENCE: { final Tree childTree = tree.getChild(0); final int childTreeType = childTree.getType(); if (childTreeType == FIELD_NAME) { final String descendantName = childTree.getChild(0).getText(); return new DescendantFieldPath(descendantName, parent, absolute); } else { throw new RecordPathException("Expected field name following '//' Token but found " + childTree); } } case PARENT_REFERENCE: { return new ParentPath(parent, absolute); } case CURRENT_FIELD: { return new CurrentFieldPath(parent, absolute); } case STRING_LITERAL: { return new LiteralValuePath(parent, tree.getText(), absolute); } case NUMBER: { return new LiteralValuePath(parent, Integer.parseInt(tree.getText()), absolute); } case PREDICATE: { final Tree operatorTree = tree.getChild(0); final RecordPathFilter filter = createFilter(operatorTree, parent, absolute); return new PredicatePath(parent, filter, absolute); } case RELATIVE_PATH: { return compile(tree, parent, absolute); } case PATH: { return compile(tree, new RootPath(), absolute); } } throw new RecordPathException("Encountered unexpected token " + tree); } private static RecordPathFilter createFilter(final Tree operatorTree, final RecordPathSegment parent, final boolean absolute) { switch (operatorTree.getType()) { case EQUAL: return createBinaryOperationFilter(operatorTree, parent, EqualsFilter::new, absolute); case NOT_EQUAL: return createBinaryOperationFilter(operatorTree, parent, NotEqualsFilter::new, absolute); case LESS_THAN: return createBinaryOperationFilter(operatorTree, parent, LessThanFilter::new, absolute); case LESS_THAN_EQUAL: return createBinaryOperationFilter(operatorTree, parent, LessThanOrEqualFilter::new, absolute); case GREATER_THAN: return createBinaryOperationFilter(operatorTree, parent, GreaterThanFilter::new, absolute); case GREATER_THAN_EQUAL: return createBinaryOperationFilter(operatorTree, parent, GreaterThanOrEqualFilter::new, absolute); default: throw new RecordPathException("Expected an Expression of form <value> <operator> <value> to follow '[' Token but found " + operatorTree); } } private static RecordPathFilter createBinaryOperationFilter(final Tree operatorTree, final RecordPathSegment parent, final BiFunction<RecordPathSegment, RecordPathSegment, RecordPathFilter> function, final boolean absolute) { final Tree lhsTree = operatorTree.getChild(0); final Tree rhsTree = operatorTree.getChild(1); final RecordPathSegment lhsPath = buildPath(lhsTree, parent, absolute); final RecordPathSegment rhsPath = buildPath(rhsTree, parent, absolute); return function.apply(lhsPath, rhsPath); } }