/* * Copyright 2015, The OpenNMS Group * * 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.opennms.newts.cassandra.search; import java.util.List; import java.util.regex.Pattern; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; /** * A splitter that supports escaping the separator. * * @author jwhite */ public class EscapableResourceIdSplitter implements ResourceIdSplitter { private static final Pattern MATCH_COLON = Pattern.compile(":"); private static final Pattern MATCH_BACKSLASH = Pattern.compile("\\\\"); private static final Pattern MATCH_ESCAPED_COLON = Pattern.compile("\\\\:"); private static final Pattern MATCH_ESCAPED_BACKSLASH = Pattern.compile("\\\\\\\\"); /** * Splits a resource id into a list of elements. * * Elements in the resource id are delimited by colons (:). * Colons can be escaped by a backslash (\:). * Backslashes are escaped when paired (\\). * * For example, the following resources id: * a:b\::c\\:d * will result in the following elements: * a, b:, c\, d * * @param id the resource id * @return a list of elements */ @Override public List<String> splitIdIntoElements(String id) { Preconditions.checkNotNull(id, "id argument"); List<String> elements = Lists.newArrayList(); int startOfNextElement = 0; int numConsecutiveEscapeCharacters = 0; char[] idChars = id.toCharArray(); for (int i = 0; i < idChars.length; i++) { // If we hit a separator, only treat it as such if it is preceded by an even number of escape characters if(idChars[i] == SEPARATOR && numConsecutiveEscapeCharacters % 2 == 0) { maybeAddSanitizedElement(new String(idChars, startOfNextElement, i-startOfNextElement), elements); startOfNextElement = i+1; } if (idChars[i] == '\\') { numConsecutiveEscapeCharacters++; } else { numConsecutiveEscapeCharacters = 0; } } maybeAddSanitizedElement(new String(idChars, startOfNextElement, idChars.length - startOfNextElement), elements); return elements; } /** * Maybe adds to element to the list after being sanitized. */ private static void maybeAddSanitizedElement(final String element, final List<String> elements) { // Trim the element and skip it when empty String sanitizedElement = element.trim(); if (sanitizedElement.length() == 0) { return; } // \: -> : sanitizedElement = MATCH_ESCAPED_COLON.matcher(sanitizedElement).replaceAll(":"); // \\ -> \ sanitizedElement = MATCH_ESCAPED_BACKSLASH.matcher(sanitizedElement).replaceAll("\\\\"); elements.add(sanitizedElement); } /** * Joins a list of elements into a resource id, escaping * special characters if required. * * See {@link #splitIdIntoElements(String)} for details. * * @param elements a list of elements * @return the resource id */ @Override public String joinElementsToId(List<String> elements) { Preconditions.checkNotNull(elements, "elements argument"); StringBuilder sb = new StringBuilder(); for (String el : elements) { // Skip null elements if (el == null) { continue; } // Trim the elements and skip any empty ones String trimmedEl = el.trim(); if (trimmedEl.length() < 1) { continue; } // \ -> \\ trimmedEl = MATCH_BACKSLASH.matcher(trimmedEl).replaceAll("\\\\\\\\"); // : -> \: trimmedEl = MATCH_COLON.matcher(trimmedEl).replaceAll("\\\\:"); if (sb.length() > 0) { sb.append(SEPARATOR); } sb.append(trimmedEl); } return sb.toString(); } }