/* * Copyright 2013 Gordon Burgett and individual contributors * * 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. */ package org.xflatdb.xflat.util; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.jdom2.xpath.XPathExpression; /** * Implements a Hamcrest {@link org.hamcrest.Matcher} for {@link XPathExpression} objects * that is namespace-aware and can translate namespace prefixes. * <p/> * According to this matcher, in the following example the two XPath expressions are equal: * <p/> * <code> * a:abc xmlns:a = "http://www.example.com"<br/> * b:abc xmlns:b = "http://www.example.com" * </code> * * @author Gordon */ public class XPathExpressionEqualityMatcher<U> extends TypeSafeMatcher<XPathExpression<U>> { private final XPathExpression<U> toMatch; private List<String> myExpTokens = null; private static final Pattern nsPattern = Pattern.compile("([a-zA-Z0-9\\.]+):"); /** * Creates a new XPathExpressionEqualityMatcher whose {@link #matches(java.lang.Object) } * method will return true iff the given XPathExpression is equal to the one provided here. * @param toMatch The expression to match. */ public XPathExpressionEqualityMatcher(XPathExpression<U> toMatch) { this.toMatch = toMatch; } private static List<String> tokenizeExpression(XPathExpression<?> expression){ String exp = expression.getExpression(); List<String> ret = new ArrayList<>(); Matcher matcher = nsPattern.matcher(exp); int index = 0; while(matcher.find()){ if(matcher.start() > index){ ret.add(exp.substring(index, matcher.start() - index)); } //translate namespace prefix to full ns String prefix = matcher.group(1); ret.add(expression.getNamespace(prefix).getURI()); index = matcher.end(); } if(index < exp.length()){ ret.add(exp.substring(index)); } return ret; } /** * Returns true if the matcher matches the given XPath expression. * @param item The XPath expression to match. * @return true if they are a match. */ @Override protected boolean matchesSafely(XPathExpression<U> item) { if (item == null) { return toMatch == null; } if(toMatch == null){ return false; } if(myExpTokens == null){ myExpTokens = tokenizeExpression(toMatch); } return equals(toMatch, myExpTokens, item, tokenizeExpression(item)); } /** * Describes this matcher to the given description. * @param description */ @Override public void describeTo(Description description) { if (toMatch == null) { description.appendText("null XPath expression"); return; } description.appendText("XPath expression equal to ").appendText(toMatch.getExpression()); } private static boolean equals(XPathExpression<?> left, List<String> leftSideTokens, XPathExpression<?> right, List<String> rightSideTokens){ if(rightSideTokens.size() != leftSideTokens.size()){ return false; } for(int i = 0; i < leftSideTokens.size(); i++){ if(!leftSideTokens.get(i).equals(rightSideTokens.get(i))){ return false; } } return true; } /** * Compares two XPath expressions for equality by tokenizing their expressions * and expanding namespace prefixes. * @param left One XPath expression to compare for equality * @param right The other XPath expression to compare for equality. * @return true iff the two expressions are equal. */ public static boolean equals(XPathExpression<?> left, XPathExpression<?> right){ if (left == null) { return right == null; } if(right == null){ return false; } return equals(left, tokenizeExpression(left), right, tokenizeExpression(right)); } }