/* * Copyright 2015-present Facebook, Inc. * * 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 com.facebook.buck.util.network; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; /** * Use this formatter as you would use a Builder to create Hive formatted rows that will transmit * correctly according to Scribe/Hive protocol. */ public final class HiveRowFormatter { private static final String COLUMN_SEPARATOR = "\001"; /** * In reality scribe/hive can encode further nested levels (array<array<string>>) by increasing * the ASCII value all the way up to \010. In some codebases, that seems to be the limit. However, * for our purpose here we will keep it simple with just one level. */ private static final String ARRAY_SEPARATOR = "\002"; private static final Function<Object, String> ESCAPE_FUNCTION = input -> { if (input == null) { return ""; } return escapeHiveString(input.toString()); }; private final StringBuilder row; private HiveRowFormatter() { row = new StringBuilder(); } public static HiveRowFormatter newFormatter() { return new HiveRowFormatter(); } public <T> HiveRowFormatter appendString(T value) { if (row.length() > 0) { row.append(COLUMN_SEPARATOR); } row.append(escapeHiveString(value.toString())); return this; } public <T> HiveRowFormatter appendStringIterable(Iterable<T> valueArray) { if (row.length() > 0) { row.append(COLUMN_SEPARATOR); } Iterable<String> escapedValues = Iterables.transform(valueArray, ESCAPE_FUNCTION); row.append(Joiner.on(ARRAY_SEPARATOR).join(escapedValues)); return this; } public String build() { return toString(); } @Override public String toString() { return row.toString(); } private static String escapeHiveString(String unescaped) { // Hive-escape in a way the original string is reversible if needed: // \001 = column divider // \r\n = rows divider // \\ = needs to be escaped to be able to reverse back into the original string return unescaped .replace("\\", "\\\\") .replace(COLUMN_SEPARATOR, "\\001") .replace(ARRAY_SEPARATOR, "\\002") .replace("\n", "\\n") .replace("\r", "\\r"); } }