TimelineReaderUtils.java
/**
* 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.hadoop.yarn.server.timelineservice.reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.classification.VisibleForTesting;
/**
* Set of utility methods to be used across timeline reader.
*/
public final class TimelineReaderUtils {
private TimelineReaderUtils() {
}
/**
* Default delimiter for joining strings.
*/
@VisibleForTesting
public static final char DEFAULT_DELIMITER_CHAR = '!';
/**
* Default escape character used for joining strings.
*/
@VisibleForTesting
public static final char DEFAULT_ESCAPE_CHAR = '*';
public static final String FROMID_KEY = "FROM_ID";
@VisibleForTesting
public static final String UID_KEY = "UID";
/**
* Split the passed string along the passed delimiter character while looking
* for escape char to interpret the splitted parts correctly. For delimiter or
* escape character to be interpreted as part of the string, they have to be
* escaped by putting an escape character in front.
* @param str string to be split.
* @param delimiterChar delimiter used for splitting.
* @param escapeChar delimiter and escape character will be escaped using this
* character.
* @return a list of strings after split.
* @throws IllegalArgumentException if string is not properly escaped.
*/
static List<String> split(final String str, final char delimiterChar,
final char escapeChar) throws IllegalArgumentException {
if (str == null) {
return null;
}
int len = str.length();
if (len == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>();
// Keeps track of offset of the passed string.
int offset = 0;
// Indicates start offset from which characters will be copied from original
// string to destination string. Resets when an escape or delimiter char is
// encountered.
int startOffset = 0;
StringBuilder builder = new StringBuilder(len);
// Iterate over the string till we reach the end.
while (offset < len) {
if (str.charAt(offset) == escapeChar) {
// An escape character must be followed by a delimiter or escape char
// but we have reached the end and have no further character to look at.
if (offset + 1 >= len) {
throw new IllegalArgumentException(
"Escape char not properly escaped.");
}
char nextChar = str.charAt(offset + 1);
// Next character must be a delimiter or an escape char.
if (nextChar != escapeChar && nextChar != delimiterChar) {
throw new IllegalArgumentException(
"Escape char or delimiter char not properly escaped.");
}
// Copy contents from the offset where last escape or delimiter char was
// encountered.
if (startOffset < offset) {
builder.append(str.substring(startOffset, offset));
}
builder.append(nextChar);
offset += 2;
// Reset the start offset as an escape char has been encountered.
startOffset = offset;
continue;
} else if (str.charAt(offset) == delimiterChar) {
// A delimiter has been encountered without an escape character.
// String needs to be split here. Copy remaining chars and add the
// string to list.
builder.append(str.substring(startOffset, offset));
list.add(builder.toString().trim());
// Reset the start offset as a delimiter has been encountered.
startOffset = ++offset;
builder = new StringBuilder(len - offset);
continue;
}
offset++;
}
// Copy rest of the characters.
if (!str.isEmpty()) {
builder.append(str.substring(startOffset));
}
// Add the last part of delimited string to list.
list.add(builder.toString().trim());
return list;
}
private static String escapeString(final String str, final char delimiterChar,
final char escapeChar) {
if (str == null) {
return null;
}
int len = str.length();
if (len == 0) {
return "";
}
StringBuilder builder = new StringBuilder();
// Keeps track of offset of the passed string.
int offset = 0;
// Indicates start offset from which characters will be copied from original
// string to destination string. Resets when an escape or delimiter char is
// encountered.
int startOffset = 0;
// Iterate over the string till we reach the end.
while (offset < len) {
char charAtOffset = str.charAt(offset);
if (charAtOffset == escapeChar || charAtOffset == delimiterChar) {
// If an escape or delimiter character is encountered, copy characters
// from the offset where escape or delimiter was last encountered.
if (startOffset < offset) {
builder.append(str.substring(startOffset, offset));
}
// Append escape char before delimiter/escape char.
builder.append(escapeChar).append(charAtOffset);
// Reset start offset for copying characters when next escape/delimiter
// char is encountered.
startOffset = offset + 1;
}
offset++;
}
// Copy remaining characters.
builder.append(str.substring(startOffset));
return builder.toString();
}
/**
* Join different strings in the passed string array delimited by passed
* delimiter with delimiter and escape character escaped using passed escape
* char.
* @param strs strings to be joined.
* @param delimiterChar delimiter used to join strings.
* @param escapeChar escape character used to escape delimiter and escape
* char.
* @return a single string joined using delimiter and properly escaped.
*/
static String joinAndEscapeStrings(final String[] strs,
final char delimiterChar, final char escapeChar) {
int len = strs.length;
// Escape each string in string array.
for (int index = 0; index < len; index++) {
if (strs[index] == null) {
return null;
}
strs[index] = escapeString(strs[index], delimiterChar, escapeChar);
}
// Join the strings after they have been escaped.
return StringUtils.join(strs, delimiterChar);
}
public static List<String> split(final String str)
throws IllegalArgumentException {
return split(str, DEFAULT_DELIMITER_CHAR, DEFAULT_ESCAPE_CHAR);
}
public static String joinAndEscapeStrings(final String[] strs) {
return joinAndEscapeStrings(strs, DEFAULT_DELIMITER_CHAR,
DEFAULT_ESCAPE_CHAR);
}
}