Repository: incubator-falcon Updated Branches: refs/heads/master 9827ac9e1 -> 463f85489
FALCON-823 Add path matching ability to the radix tree. Contributed by Ajay Yadav Project: http://git-wip-us.apache.org/repos/asf/incubator-falcon/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-falcon/commit/06ffdf91 Tree: http://git-wip-us.apache.org/repos/asf/incubator-falcon/tree/06ffdf91 Diff: http://git-wip-us.apache.org/repos/asf/incubator-falcon/diff/06ffdf91 Branch: refs/heads/master Commit: 06ffdf913fed07dcb8eee81f4aa0f0779b051ed0 Parents: 9827ac9 Author: srikanth.sundarrajan <srik...@apache.org> Authored: Fri Dec 26 10:34:16 2014 +0530 Committer: srikanth.sundarrajan <srik...@apache.org> Committed: Fri Dec 26 10:34:16 2014 +0530 ---------------------------------------------------------------------- CHANGES.txt | 3 + .../falcon/entity/common/FeedDataPath.java | 15 +- .../falcon/entity/store/FeedPathStore.java | 5 + .../apache/falcon/util/FalconRadixUtils.java | 314 +++++++++++++++++++ .../java/org/apache/falcon/util/RadixNode.java | 23 ++ .../java/org/apache/falcon/util/RadixTree.java | 38 ++- .../apache/falcon/entity/FeedDataPathTest.java | 124 ++++++++ .../org/apache/falcon/util/RadixNodeTest.java | 18 +- .../org/apache/falcon/util/RadixTreeTest.java | 22 ++ 9 files changed, 544 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-falcon/blob/06ffdf91/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 5d529b9..af4bd9e 100755 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,9 @@ Trunk (Unreleased) NEW FEATURES IMPROVEMENTS + FALCON-823 Add path matching ability to the radix tree (Ajay Yadav + via Srikanth Sundarrajan) + FALCON-329 Falcon client methods should return objects. (Samar via Shwetha GS) FALCON-593 Preserve data type for properties in a vertex. (Ajay http://git-wip-us.apache.org/repos/asf/incubator-falcon/blob/06ffdf91/common/src/main/java/org/apache/falcon/entity/common/FeedDataPath.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/falcon/entity/common/FeedDataPath.java b/common/src/main/java/org/apache/falcon/entity/common/FeedDataPath.java index 39e636b..6ededbb 100644 --- a/common/src/main/java/org/apache/falcon/entity/common/FeedDataPath.java +++ b/common/src/main/java/org/apache/falcon/entity/common/FeedDataPath.java @@ -30,14 +30,25 @@ public final class FeedDataPath { * Standard variables for feed time components. */ public static enum VARS { - YEAR("yyyy"), MONTH("MM"), DAY("dd"), HOUR("HH"), MINUTE("mm"); + YEAR("yyyy", "([0-9]{4})"), MONTH("MM", "(0[1-9]|1[0-2])"), DAY("dd", "(0[1-9]|1[0-9]|2[0-9]|3[0-1])"), + HOUR("HH", "([0-1][0-9]|2[0-4])"), MINUTE("mm", "([0-5][0-9]|60)"); private final Pattern pattern; private final String datePattern; + private final String patternRegularExpression; - private VARS(String datePattern) { + private VARS(String datePattern, String patternRegularExpression) { pattern = Pattern.compile("\\$\\{" + name() + "\\}"); this.datePattern = datePattern; + this.patternRegularExpression = patternRegularExpression; + } + + public String getPatternRegularExpression() { + return patternRegularExpression; + } + + public String getDatePattern() { + return datePattern; } public String regex() { http://git-wip-us.apache.org/repos/asf/incubator-falcon/blob/06ffdf91/common/src/main/java/org/apache/falcon/entity/store/FeedPathStore.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/falcon/entity/store/FeedPathStore.java b/common/src/main/java/org/apache/falcon/entity/store/FeedPathStore.java index 97d21c1..1be12fe 100644 --- a/common/src/main/java/org/apache/falcon/entity/store/FeedPathStore.java +++ b/common/src/main/java/org/apache/falcon/entity/store/FeedPathStore.java @@ -18,6 +18,8 @@ package org.apache.falcon.entity.store; +import org.apache.falcon.util.FalconRadixUtils; + import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Collection; @@ -34,6 +36,9 @@ public interface FeedPathStore<T> { int getSize(); @Nullable + Collection<T> find(@Nonnull String key, @Nonnull FalconRadixUtils.INodeAlgorithm algorithm); + + @Nullable Collection<T> find(@Nonnull String key); boolean delete(@Nonnull String key, @Nonnull T value); http://git-wip-us.apache.org/repos/asf/incubator-falcon/blob/06ffdf91/common/src/main/java/org/apache/falcon/util/FalconRadixUtils.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/falcon/util/FalconRadixUtils.java b/common/src/main/java/org/apache/falcon/util/FalconRadixUtils.java new file mode 100644 index 0000000..bbd73c7 --- /dev/null +++ b/common/src/main/java/org/apache/falcon/util/FalconRadixUtils.java @@ -0,0 +1,314 @@ +/** + * 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.falcon.util; + +import org.apache.commons.lang.StringUtils; +import org.apache.falcon.entity.common.FeedDataPath; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Falcon specific utilities for the Radix Tree. + */ +public class FalconRadixUtils { + + /** + * This interface implements the various algorithms to compare node's key with input based on whether you want + * a regular expression based algorithm or a character by character matching algorithm. + */ + public interface INodeAlgorithm { + + /** + * Checks if the given key and input match. + * @param key key of the node + * @param input input String to be matched against key. + * @return true if key and input match. + */ + boolean match(String key, String input); + + boolean startsWith(String key, String input); + + /** + * Finds next node to take for traversal among currentNode's children. + * @param currentNode of RadixTree which has been matched. + * @param input input String to be searched. + * @return Node to be traversed next. + */ + RadixNode getNextCandidate(RadixNode currentNode, String input); + + // for the given node and input key, finds the remainingText to be matched with child sub tree. + String getRemainingText(RadixNode currentNode, String key); + } + + /** + * This Algorithm does a plain string comparison for all + * type of operations on a node. + */ + static class StringAlgorithm implements INodeAlgorithm { + + @Override + public boolean match(String key, String input) { + return StringUtils.equals(key, input); + } + + @Override + public boolean startsWith(String nodeKey, String inputKey) { + return inputKey.startsWith(nodeKey); + } + + @Override + public RadixNode getNextCandidate(RadixNode currentNode, String input) { + RadixNode newRoot = null; + String remainingText = input.substring(currentNode.getKey().length()); + List<RadixNode> result = currentNode.getChildren(); + for(RadixNode child : result){ + if (child.getKey().charAt(0) == remainingText.charAt(0)){ + newRoot = child; + break; + } + } + return newRoot; + } + + @Override + public String getRemainingText(RadixNode currentNode, String key) { + return key.substring(currentNode.getKey().length()); + } + + + } + + static class FeedRegexAlgorithm implements INodeAlgorithm { + + /** + * This function matches a feed path template with feed instance's path string. + * + * Key is assumed to be a feed's path template and inputString is assumed to be an instance's path string. + * Variable/Regex parts of the feed's template are matched against the corresponding parts in inputString + * using regular expression and for other parts a character by character match is performed. + * e.g. Given templateString (/data/cas/${YEAR}/${MONTH}/${DAY}) and inputString (/data/cas/2014/09/09) + * the function will return true. + * @param templateString Node's key (Feed's template path) + * @param inputString inputString String to be matched against templateString(instance's path) + * @return true if the templateString and inputString match, false otherwise. + */ + @Override + public boolean match(String templateString, String inputString) { + if (StringUtils.isBlank(templateString)) { + return false; + } + // Divide the templateString and inputString into templateParts of regex and character matches + List<String> templateParts = getPartsInPathTemplate(templateString); + List<String> inputStringParts = getCorrespondingParts(inputString, templateParts); + + if (inputStringParts.size() != templateParts.size()) { + return false; + } + + int counter = 0; + while (counter < inputStringParts.size()) { + if (!matchPart(templateParts.get(counter), inputStringParts.get(counter))) { + return false; + } + counter++; + } + return true; + } + + + /** + * + * Finds if the current node's key is a prefix of the given inputString or not. + * + * @param inputTemplate inputTemplate String + * @param inputString inputString to be checked + * @return true if inputString starts with inputTemplate, false otherwise. + */ + @Override + public boolean startsWith(String inputTemplate, String inputString) { + + if (StringUtils.isBlank(inputString)) { + return false; + } + if (StringUtils.isBlank(inputTemplate)) { + return true; + } + + // divide inputTemplate and inputString into corresponding templateParts of regex and character only strings + List<String> templateParts = getPartsInPathTemplate(inputTemplate); + List<String> remainingPattern = getCorrespondingParts(inputString, templateParts); + + if (templateParts.size() > remainingPattern.size()) { + return false; + } + + int counter = 0; + // compare part by part till the templateParts end + for (String templatePart : templateParts) { + String part = remainingPattern.get(counter); + if (!matchPart(templatePart, part)) { + return false; + } + counter++; + } + return true; + } + + @Override + public RadixNode getNextCandidate(RadixNode currentNode, String input) { + RadixNode newRoot = null; + // replace the regex with pattern's length + String remainingText = input.substring(getPatternsEffectiveLength(currentNode.getKey())); + List<RadixNode> result = currentNode.getChildren(); + for(RadixNode child : result) { + String key = child.getKey(); + if (key.startsWith("${")) { + // get the regex + String regex = key.substring(0, key.indexOf("}") + 1); + // match the text and the regex + FeedDataPath.VARS var = getMatchingRegex(regex); + if (matchPart(regex, input.substring(0, var.getDatePattern().length()))) { + newRoot = child; // if it matches then this is the newRoot + break; + } + } else if (child.getKey().charAt(0) == remainingText.charAt(0)) { + newRoot = child; + break; + } + } + return newRoot; + } + + @Override + public String getRemainingText(RadixNode currentNode, String inputString) { + // find the match length for current inputString + return inputString.substring(getPatternsEffectiveLength(currentNode.getKey())); + } + + private int getPatternsEffectiveLength(String templateString) { + if (StringUtils.isBlank(templateString)) { + return 0; + } + for (FeedDataPath.VARS var : FeedDataPath.VARS.values()) { + templateString = templateString.replace("${" + var.name() + "}", var.getDatePattern()); + } + return templateString.length(); + } + + /** + * Divide a given template string into parts of regex and character strings + * e.g. /data/cas/${YEAR}/${MONTH}/${DAY} will be converted to + * [/data/cas/, ${YEAR}, /, ${MONTH}, /, ${DAY}] + * @param templateString input string representing a feed's path template + * @return list of parts in input templateString which are either completely regex or normal string. + */ + private List<String> getPartsInPathTemplate(String templateString) { + //divide the node's templateString in parts of regular expression and normal string + List<String> parts = new ArrayList<String>(); + Matcher matcher = FeedDataPath.PATTERN.matcher(templateString); + int currentIndex = 0; + while (matcher.find()) { + parts.add(templateString.substring(currentIndex, matcher.start())); + parts.add(matcher.group()); + currentIndex = matcher.end(); + } + if (currentIndex != templateString.length()) { + parts.add(templateString.substring(currentIndex)); + } + return Collections.unmodifiableList(parts); + } + + + private FeedDataPath.VARS getMatchingRegex(String inputPart) { + //inputPart will be something like ${YEAR} + inputPart = inputPart.replace("${", "\\$\\{"); + inputPart = inputPart.replace("}", "\\}"); + + for (FeedDataPath.VARS var : FeedDataPath.VARS.values()) { + if (StringUtils.equals(inputPart, var.regex())) { + return var; + } + } + return null; + } + + + /** + * Divides a string into corresponding parts for the template to carry out comparison. + * templateParts = [/data/cas/, ${YEAR}, /, ${MONTH}, /, ${DAY}] + * inputString = /data/cas/2014/09/09 + * returns [/data/cas/, 2014, /, 09, /, 09] + * @param inputString normal string representing feed instance path + * @param templateParts parts of feed's path template broken into regex and non-regex units. + * @return a list of strings where each part of the list corresponds to a part in list of template parts. + */ + private List<String> getCorrespondingParts(String inputString, List<String> templateParts) { + List<String> stringParts = new ArrayList<String>(); + int counter = 0; + while (StringUtils.isNotBlank(inputString) && counter < templateParts.size()) { + String currentTemplatePart = templateParts.get(counter); + int length = Math.min(getPatternsEffectiveLength(currentTemplatePart), inputString.length()); + stringParts.add(inputString.substring(0, length)); + inputString = inputString.substring(length); + counter++; + } + if (StringUtils.isNotBlank(inputString)) { + stringParts.add(inputString); + } + return stringParts; + } + + /** + * Compare a pure regex or pure string part with a given string. + * + * @param template template part, which can either be a pure regex or pure non-regex string. + * @param input input String to be matched against the template part. + * @return true if the input string matches the template, in case of a regex component a regex comparison is + * made, else a character by character comparison is made. + */ + private boolean matchPart(String template, String input) { + if (template.startsWith("${")) { // if the part begins with ${ then it's a regex part, do regex match + template = template.replace("${", "\\$\\{"); + template = template.replace("}", "\\}"); + for (FeedDataPath.VARS var : FeedDataPath.VARS.values()) {//find which regex is this + if (StringUtils.equals(var.regex(), template)) {// regex found, do matching + //find part of the input string which should be matched against regex + String desiredPart = input.substring(0, var.getDatePattern().length()); + Pattern pattern = Pattern.compile(var.getPatternRegularExpression()); + Matcher matcher = pattern.matcher(desiredPart); + if (!matcher.matches()) { + return false; + } + return true; + } + } + return false; + } else {// do exact match with normal strings + if (!input.startsWith(template)) { + return false; + } + } + return true; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-falcon/blob/06ffdf91/common/src/main/java/org/apache/falcon/util/RadixNode.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/falcon/util/RadixNode.java b/common/src/main/java/org/apache/falcon/util/RadixNode.java index 564df8e..12227cb 100644 --- a/common/src/main/java/org/apache/falcon/util/RadixNode.java +++ b/common/src/main/java/org/apache/falcon/util/RadixNode.java @@ -124,4 +124,27 @@ public class RadixNode<T> { return matchLength; } + + + /** + * Finds the length of the match between node's key and input. + * + * It can do either a character by character match or a regular expression match(used to match a feed instance path + * with feed location template). Only regular expressions allowed in the feed path are evaluated for matching. + * @param input input string to be matched with the key of the node. + * @param matcher A custom matcher algorithm to match node's key against the input. It is used when matching + * path of a Feed's instance to Feed's path template. + * @return + */ + public boolean matches(String input, FalconRadixUtils.INodeAlgorithm matcher) { + if (input == null) { + return false; + } + + if (matcher == null) { + return StringUtils.equals(getKey(), input); + } + + return matcher.match(this.getKey(), input); + } } http://git-wip-us.apache.org/repos/asf/incubator-falcon/blob/06ffdf91/common/src/main/java/org/apache/falcon/util/RadixTree.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/falcon/util/RadixTree.java b/common/src/main/java/org/apache/falcon/util/RadixTree.java index 6dbe160..6cd79f5 100644 --- a/common/src/main/java/org/apache/falcon/util/RadixTree.java +++ b/common/src/main/java/org/apache/falcon/util/RadixTree.java @@ -189,21 +189,35 @@ public class RadixTree<T> implements FeedPathStore<T>, Formattable { */ @Override @Nullable - public synchronized Collection<T> find(@Nonnull String key) { - if (key != null && !key.trim().isEmpty()){ - return recursiveFind(key.trim(), root); + public synchronized Collection<T> find(@Nonnull String key, FalconRadixUtils.INodeAlgorithm algorithm) { + if (key != null && !key.trim().isEmpty()) { + if (algorithm == null) { + algorithm = new FalconRadixUtils.StringAlgorithm(); + } + return recursiveFind(key.trim(), root, algorithm); + } + return null; + } + + @Nullable + @Override + public Collection<T> find(@Nonnull String key) { + if (key != null && !key.trim().isEmpty()) { + FalconRadixUtils.INodeAlgorithm algorithm = new FalconRadixUtils.StringAlgorithm(); + return recursiveFind(key.trim(), root, algorithm); } return null; } - private Collection<T> recursiveFind(String key, RadixNode<T> currentNode){ + private Collection<T> recursiveFind(String key, RadixNode<T> currentNode, + FalconRadixUtils.INodeAlgorithm algorithm){ - if (!key.startsWith(currentNode.getKey())){ + if (!algorithm.startsWith(currentNode.getKey(), key)){ LOG.debug("Current Node key: {} is not a prefix in the input key: {}", currentNode.getKey(), key); return null; } - if (StringUtils.equals(key, currentNode.getKey())){ + if (algorithm.match(currentNode.getKey(), key)){ if (currentNode.isTerminal()){ LOG.debug("Found the terminal node with key: {} for the given input.", currentNode.getKey()); return currentNode.getValues(); @@ -214,21 +228,15 @@ public class RadixTree<T> implements FeedPathStore<T>, Formattable { } //find child to follow, using remaining Text - RadixNode<T> newRoot = null; - String remainingText = key.substring(currentNode.getKey().length()); - for(RadixNode<T> child : currentNode.getChildren()){ - if (child.getKey().charAt(0) == remainingText.charAt(0)){ - newRoot = child; - break; - } - } + RadixNode<T> newRoot = algorithm.getNextCandidate(currentNode, key); + String remainingText = algorithm.getRemainingText(currentNode, key); if (newRoot == null){ LOG.debug("No child found to follow for further processing. Current node key {}"); return null; }else { LOG.debug("Recursing with new key: {} and new remainingText: {}", newRoot.getKey(), remainingText); - return recursiveFind(remainingText, newRoot); + return recursiveFind(remainingText, newRoot, algorithm); } } http://git-wip-us.apache.org/repos/asf/incubator-falcon/blob/06ffdf91/common/src/test/java/org/apache/falcon/entity/FeedDataPathTest.java ---------------------------------------------------------------------- diff --git a/common/src/test/java/org/apache/falcon/entity/FeedDataPathTest.java b/common/src/test/java/org/apache/falcon/entity/FeedDataPathTest.java new file mode 100644 index 0000000..c405556 --- /dev/null +++ b/common/src/test/java/org/apache/falcon/entity/FeedDataPathTest.java @@ -0,0 +1,124 @@ +/** + * 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.falcon.entity; + +import org.apache.falcon.entity.common.FeedDataPath; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * + */ +public class FeedDataPathTest { + + @Test + public void testMinutesRegularExpression() { + String monthPattern = FeedDataPath.VARS.MINUTE.getPatternRegularExpression(); + Assert.assertFalse("0".matches(monthPattern)); + Assert.assertFalse("1".matches(monthPattern)); + Assert.assertFalse("61".matches(monthPattern)); + Assert.assertFalse("010".matches(monthPattern)); + Assert.assertFalse("10 ".matches(monthPattern)); + Assert.assertFalse(" 10".matches(monthPattern)); + + + Assert.assertTrue("00".matches(monthPattern)); + Assert.assertTrue("01".matches(monthPattern)); + Assert.assertTrue("60".matches(monthPattern)); + } + + @Test + public void testHourRegularExpression() { + String hourPattern = FeedDataPath.VARS.HOUR.getPatternRegularExpression(); + Assert.assertFalse("0".matches(hourPattern)); + Assert.assertFalse("1".matches(hourPattern)); + Assert.assertFalse("2".matches(hourPattern)); + Assert.assertFalse("25".matches(hourPattern)); + Assert.assertFalse("29".matches(hourPattern)); + Assert.assertFalse("010".matches(hourPattern)); + Assert.assertFalse("10 ".matches(hourPattern)); + Assert.assertFalse(" 10".matches(hourPattern)); + + + Assert.assertTrue("00".matches(hourPattern)); + Assert.assertTrue("01".matches(hourPattern)); + Assert.assertTrue("24".matches(hourPattern)); + Assert.assertTrue("10".matches(hourPattern)); + Assert.assertTrue("19".matches(hourPattern)); + Assert.assertTrue("12".matches(hourPattern)); + } + + + @Test + public void testDayRegularExpression() { + String dayPattern = FeedDataPath.VARS.DAY.getPatternRegularExpression(); + Assert.assertFalse("0".matches(dayPattern)); + Assert.assertFalse("1".matches(dayPattern)); + Assert.assertFalse("32".matches(dayPattern)); + Assert.assertFalse("00".matches(dayPattern)); + Assert.assertFalse("010".matches(dayPattern)); + Assert.assertFalse("10 ".matches(dayPattern)); + Assert.assertFalse(" 10".matches(dayPattern)); + + + Assert.assertTrue("01".matches(dayPattern)); + Assert.assertTrue("10".matches(dayPattern)); + Assert.assertTrue("29".matches(dayPattern)); + Assert.assertTrue("30".matches(dayPattern)); + Assert.assertTrue("31".matches(dayPattern)); + } + + @Test + public void testMonthRegularExpression() { + String monthPattern = FeedDataPath.VARS.MONTH.getPatternRegularExpression(); + Assert.assertFalse("0".matches(monthPattern)); + Assert.assertFalse("1".matches(monthPattern)); + Assert.assertFalse("13".matches(monthPattern)); + Assert.assertFalse("19".matches(monthPattern)); + Assert.assertFalse("00".matches(monthPattern)); + Assert.assertFalse("010".matches(monthPattern)); + Assert.assertFalse("10 ".matches(monthPattern)); + Assert.assertFalse(" 10".matches(monthPattern)); + + + Assert.assertTrue("01".matches(monthPattern)); + Assert.assertTrue("02".matches(monthPattern)); + Assert.assertTrue("10".matches(monthPattern)); + Assert.assertTrue("12".matches(monthPattern)); + } + + @Test + public void testYearRegularExpression() { + String monthPattern = FeedDataPath.VARS.YEAR.getPatternRegularExpression(); + Assert.assertFalse("0".matches(monthPattern)); + Assert.assertFalse("1".matches(monthPattern)); + Assert.assertFalse("13".matches(monthPattern)); + Assert.assertFalse("19".matches(monthPattern)); + Assert.assertFalse("00".matches(monthPattern)); + Assert.assertFalse("010".matches(monthPattern)); + Assert.assertFalse("10 ".matches(monthPattern)); + Assert.assertFalse(" 10".matches(monthPattern)); + + + Assert.assertTrue("0001".matches(monthPattern)); + Assert.assertTrue("2014".matches(monthPattern)); + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-falcon/blob/06ffdf91/common/src/test/java/org/apache/falcon/util/RadixNodeTest.java ---------------------------------------------------------------------- diff --git a/common/src/test/java/org/apache/falcon/util/RadixNodeTest.java b/common/src/test/java/org/apache/falcon/util/RadixNodeTest.java index 4f63806..aea28e6 100644 --- a/common/src/test/java/org/apache/falcon/util/RadixNodeTest.java +++ b/common/src/test/java/org/apache/falcon/util/RadixNodeTest.java @@ -19,7 +19,8 @@ package org.apache.falcon.util; import org.testng.Assert; -import org.testng.annotations.*; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.Arrays; import java.util.HashSet; @@ -90,4 +91,19 @@ public class RadixNodeTest { Assert.assertTrue(normalNode.containsValue("CAS Project")); } + @Test + public void testMatchInput() { + RadixNode<String> node = new RadixNode<String>(); + + FalconRadixUtils.INodeAlgorithm matcher = new FalconRadixUtils.FeedRegexAlgorithm(); + node.setKey("/data/cas/projects/${YEAR}/${MONTH}/${DAY}"); + Assert.assertTrue(node.matches("/data/cas/projects/2014/09/09", matcher)); + Assert.assertFalse(node.matches("/data/cas/projects/20140909", matcher)); + Assert.assertFalse(node.matches("/data/2014/projects/2014/09/09", matcher)); + Assert.assertFalse(node.matches("/data/2014/projects/2014/09/", matcher)); + Assert.assertFalse(node.matches("/data/cas/projects/2014/09/09trail", matcher)); + Assert.assertFalse(node.matches("/data/cas/projects/2014/09/09/", matcher)); + Assert.assertFalse(node.matches("/data/cas/projects/2014/09/", matcher)); + } + } http://git-wip-us.apache.org/repos/asf/incubator-falcon/blob/06ffdf91/common/src/test/java/org/apache/falcon/util/RadixTreeTest.java ---------------------------------------------------------------------- diff --git a/common/src/test/java/org/apache/falcon/util/RadixTreeTest.java b/common/src/test/java/org/apache/falcon/util/RadixTreeTest.java index 28589ed..109c24d 100644 --- a/common/src/test/java/org/apache/falcon/util/RadixTreeTest.java +++ b/common/src/test/java/org/apache/falcon/util/RadixTreeTest.java @@ -34,6 +34,7 @@ import java.util.List; public class RadixTreeTest { private RadixTree<String> tree; + private FalconRadixUtils.INodeAlgorithm regexAlgorithm = new FalconRadixUtils.FeedRegexAlgorithm(); @BeforeMethod public void setUp() { @@ -118,6 +119,27 @@ public class RadixTreeTest { } + //Tests for find using regular expression + @Test + public void testFindUsingRegex() { + tree.insert("/data/cas/${YEAR}/", "rtbd"); + Assert.assertTrue(tree.find("/data/cas/2014/", regexAlgorithm).contains("rtbd")); + Assert.assertNull(tree.find("/data/cas/", regexAlgorithm)); + Assert.assertNull(tree.find("/data/cas/2014/09", regexAlgorithm)); + Assert.assertNull(tree.find("/data/cas/${YEAR}/", regexAlgorithm)); + + tree.insert("/data/cas/${YEAR}/colo", "local"); + tree.insert("/data/cas/${YEAR}/colo", "duplicate-local"); + Assert.assertNull(tree.find("/data/cas/${YEAR}/", regexAlgorithm)); + Assert.assertNull(tree.find("/data/cas/${YEAR}/colo", regexAlgorithm)); + Assert.assertNull(tree.find("/data/cas/", regexAlgorithm)); + Assert.assertTrue(tree.find("/data/cas/2014/", regexAlgorithm).contains("rtbd")); + Assert.assertTrue(tree.find("/data/cas/2014/colo", regexAlgorithm).contains("local")); + Assert.assertTrue(tree.find("/data/cas/2014/colo", regexAlgorithm).contains("duplicate-local")); + + + } + // Tests for delete method @Test public void testDeleteChildOfTerminal() {