| /* |
| * 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.flink.configuration; |
| |
| import org.apache.flink.annotation.Internal; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import static org.apache.flink.util.Preconditions.checkNotNull; |
| |
| /** Helper class for splitting a string on a given delimiter with quoting logic. */ |
| @Internal |
| class StructuredOptionsSplitter { |
| |
| /** |
| * Splits the given string on the given delimiter. It supports quoting parts of the string with |
| * either single (') or double quotes ("). Quotes can be escaped by doubling the quotes. |
| * |
| * <p>Examples: |
| * |
| * <ul> |
| * <li>'A;B';C => [A;B], [C] |
| * <li>"AB'D";B;C => [AB'D], [B], [C] |
| * <li>"AB'""D;B";C => [AB'\"D;B], [C] |
| * </ul> |
| * |
| * <p>For more examples check the tests. |
| * |
| * @param string a string to split |
| * @param delimiter delimiter to split on |
| * @return a list of splits |
| */ |
| static List<String> splitEscaped(String string, char delimiter) { |
| List<Token> tokens = tokenize(checkNotNull(string), delimiter); |
| return processTokens(tokens); |
| } |
| |
| /** |
| * Escapes the given string with single quotes, if the input string contains a double quote or |
| * any of the given {@code charsToEscape}. Any single quotes in the input string will be escaped |
| * by doubling. |
| * |
| * <p>Given that the escapeChar is (;) |
| * |
| * <p>Examples: |
| * |
| * <ul> |
| * <li>A,B,C,D => A,B,C,D |
| * <li>A'B'C'D => 'A''B''C''D' |
| * <li>A;BCD => 'A;BCD' |
| * <li>AB"C"D => 'AB"C"D' |
| * <li>AB'"D:B => 'AB''"D:B' |
| * </ul> |
| * |
| * @param string a string which needs to be escaped |
| * @param charsToEscape escape chars for the escape conditions |
| * @return escaped string by single quote |
| */ |
| static String escapeWithSingleQuote(String string, String... charsToEscape) { |
| boolean escape = |
| Arrays.stream(charsToEscape).anyMatch(string::contains) |
| || string.contains("\"") |
| || string.contains("'"); |
| |
| if (escape) { |
| return "'" + string.replaceAll("'", "''") + "'"; |
| } |
| |
| return string; |
| } |
| |
| private static List<String> processTokens(List<Token> tokens) { |
| final List<String> splits = new ArrayList<>(); |
| for (int i = 0; i < tokens.size(); i++) { |
| Token token = tokens.get(i); |
| switch (token.getTokenType()) { |
| case DOUBLE_QUOTED: |
| case SINGLE_QUOTED: |
| if (i + 1 < tokens.size() |
| && tokens.get(i + 1).getTokenType() != TokenType.DELIMITER) { |
| int illegalPosition = tokens.get(i + 1).getPosition() - 1; |
| throw new IllegalArgumentException( |
| "Could not split string. Illegal quoting at position: " |
| + illegalPosition); |
| } |
| splits.add(token.getString()); |
| break; |
| case UNQUOTED: |
| splits.add(token.getString()); |
| break; |
| case DELIMITER: |
| if (i + 1 < tokens.size() |
| && tokens.get(i + 1).getTokenType() == TokenType.DELIMITER) { |
| splits.add(""); |
| } |
| break; |
| } |
| } |
| |
| return splits; |
| } |
| |
| private static List<Token> tokenize(String string, char delimiter) { |
| final List<Token> tokens = new ArrayList<>(); |
| final StringBuilder builder = new StringBuilder(); |
| for (int cursor = 0; cursor < string.length(); ) { |
| final char c = string.charAt(cursor); |
| |
| int nextChar = cursor + 1; |
| if (c == '\'') { |
| nextChar = consumeInQuotes(string, '\'', cursor, builder); |
| tokens.add(new Token(TokenType.SINGLE_QUOTED, builder.toString(), cursor)); |
| } else if (c == '"') { |
| nextChar = consumeInQuotes(string, '"', cursor, builder); |
| tokens.add(new Token(TokenType.DOUBLE_QUOTED, builder.toString(), cursor)); |
| } else if (c == delimiter) { |
| tokens.add(new Token(TokenType.DELIMITER, String.valueOf(c), cursor)); |
| } else if (!Character.isWhitespace(c)) { |
| nextChar = consumeUnquoted(string, delimiter, cursor, builder); |
| tokens.add(new Token(TokenType.UNQUOTED, builder.toString().trim(), cursor)); |
| } |
| builder.setLength(0); |
| cursor = nextChar; |
| } |
| |
| return tokens; |
| } |
| |
| private static int consumeInQuotes( |
| String string, char quote, int cursor, StringBuilder builder) { |
| for (int i = cursor + 1; i < string.length(); i++) { |
| char c = string.charAt(i); |
| if (c == quote) { |
| if (i + 1 < string.length() && string.charAt(i + 1) == quote) { |
| builder.append(c); |
| i += 1; |
| } else { |
| return i + 1; |
| } |
| } else { |
| builder.append(c); |
| } |
| } |
| |
| throw new IllegalArgumentException( |
| "Could not split string. Quoting was not closed properly."); |
| } |
| |
| private static int consumeUnquoted( |
| String string, char delimiter, int cursor, StringBuilder builder) { |
| int i; |
| for (i = cursor; i < string.length(); i++) { |
| char c = string.charAt(i); |
| if (c == delimiter) { |
| return i; |
| } |
| |
| builder.append(c); |
| } |
| |
| return i; |
| } |
| |
| private enum TokenType { |
| DOUBLE_QUOTED, |
| SINGLE_QUOTED, |
| UNQUOTED, |
| DELIMITER |
| } |
| |
| private static class Token { |
| private final TokenType tokenType; |
| private final String string; |
| private final int position; |
| |
| private Token(TokenType tokenType, String string, int position) { |
| this.tokenType = tokenType; |
| this.string = string; |
| this.position = position; |
| } |
| |
| public TokenType getTokenType() { |
| return tokenType; |
| } |
| |
| public String getString() { |
| return string; |
| } |
| |
| public int getPosition() { |
| return position; |
| } |
| } |
| |
| private StructuredOptionsSplitter() {} |
| } |