/* * ToroDB * Copyright © 2014 8Kdata Technology (www.8kdata.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.torodb.common.util; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.List; public class TextEscaper { public interface CharacterPredicate { public boolean apply(char character); } public interface Escapable { public char getCharacter(); public char getSuffixCharacter(); public char getEscapeCharacter(); } public static TextEscaper create(CharacterPredicate isEscapable, CharacterPredicate isEscapeCharacter, Escapable[]... escapableGroups) { return new TextEscaper(isEscapable, isEscapeCharacter, escapableGroups); } private final CharacterPredicate isEscapable; private final CharacterPredicate isEscapeCharacter; private final ImmutableMap<Character, Escapable> escapables; private final ImmutableMap<Character, Escapable> escapablesBySuffix; protected TextEscaper(CharacterPredicate isEscapable, CharacterPredicate isEscapeCharacter, Escapable[]... escapableGroups) { this.isEscapable = isEscapable; this.isEscapeCharacter = isEscapeCharacter; List<Character> escapeCharacters = new ArrayList<>(); for (Escapable[] escapables : escapableGroups) { for (Escapable escapable : escapables) { if (escapable.getCharacter() == escapable.getEscapeCharacter()) { if (escapeCharacters.contains(escapable.getCharacter())) { throw new IllegalArgumentException("Escape character '" + escapable.getCharacter() + "' is repeated"); } if (!isEscapeCharacter.apply(escapable.getCharacter())) { throw new IllegalArgumentException("Escape character '" + escapable.getCharacter() + "' does not apply to isEscapeCharacter predicate"); } escapeCharacters.add(escapable.getCharacter()); } } } List<Character> escapableCharacters = new ArrayList<>(); ImmutableMap.Builder<Character, Escapable> escapablesBuilder = ImmutableMap.builder(); ImmutableMap.Builder<Character, Escapable> escapablesBySuffixBuilder = ImmutableMap.builder(); for (Escapable[] escapables : escapableGroups) { for (Escapable escapable : escapables) { if (!escapeCharacters.contains(escapable.getEscapeCharacter())) { throw new IllegalArgumentException("Undefined escape character '" + escapable .getCharacter() + "'"); } if (escapableCharacters.contains(escapable.getEscapeCharacter())) { throw new IllegalArgumentException("Escapable character '" + escapable.getCharacter() + "' is repeated"); } if (!isEscapable.apply(escapable.getCharacter())) { throw new IllegalArgumentException("Escape character '" + escapable.getCharacter() + "' does not apply to isEscapable predicate"); } escapablesBuilder.put(escapable.getCharacter(), escapable); escapablesBySuffixBuilder.put(escapable.getSuffixCharacter(), escapable); escapableCharacters.add(escapable.getCharacter()); } } this.escapables = escapablesBuilder.build(); this.escapablesBySuffix = escapablesBySuffixBuilder.build(); } public String escape(String text) { Preconditions.checkArgument(text != null, "Can not escape null"); String escapedText = text; final int textLength = text.length(); for (int index = 0; index < textLength; index++) { char textCharacter = text.charAt(index); if (isEscapable(textCharacter)) { StringBuilder escapedTextBuilder = new StringBuilder(text.substring(0, index)); for (; index < textLength; index++) { textCharacter = text.charAt(index); if (isEscapable(textCharacter)) { Escapable escapable = escapeOf(textCharacter); escapedTextBuilder.append(escapable.getEscapeCharacter()); escapedTextBuilder.append(escapable.getSuffixCharacter()); } else { escapedTextBuilder.append(textCharacter); } } escapedText = escapedTextBuilder.toString(); break; } } return escapedText; } public void appendEscaped(StringBuilder stringBuilder, String text) { Preconditions.checkArgument(text != null, "Can not escape null"); final int textLength = text.length(); for (int index = 0; index < textLength; index++) { char textCharacter = text.charAt(index); if (isEscapable(textCharacter)) { for (; index < textLength; index++) { textCharacter = text.charAt(index); if (isEscapable(textCharacter)) { Escapable escapable = escapeOf(textCharacter); stringBuilder.append(escapable.getEscapeCharacter()); stringBuilder.append(escapable.getSuffixCharacter()); } else { stringBuilder.append(textCharacter); } } break; } stringBuilder.append(textCharacter); } } public String unescape(String escapedText) { Preconditions.checkArgument(escapedText != null, "Can not unescape null"); String text = escapedText; final int escapedTextLength = escapedText.length(); for (int index = 0; index < escapedTextLength; index++) { char textCharacter = text.charAt(index); if (isEscapeCharacter(textCharacter)) { StringBuilder textBuilder = new StringBuilder(text.substring(0, index)); for (; index < escapedTextLength; index++) { textCharacter = text.charAt(index); if (isEscapeCharacter(textCharacter)) { index++; if (index >= escapedTextLength) { throw new IllegalArgumentException("Text '" + escapedText + "' contains escape character and ended without providing a suffix"); } Escapable escape = unescapeOfSuffix(text.charAt(index)); textBuilder.append(escape.getCharacter()); } else { textBuilder.append(textCharacter); } } text = textBuilder.toString(); break; } } return text; } protected boolean isEscapeCharacter(char character) { return isEscapeCharacter.apply(character); } protected boolean isEscapable(char character) { return isEscapable.apply(character); } protected Escapable escapeOf(char character) { Escapable escapable = escapables.get(character); if (escapable == null) { throw new IllegalArgumentException("Character '" + character + "' is not escapable"); } return escapable; } protected Escapable unescapeOfSuffix(char suffixCharacter) { Escapable escapable = escapablesBySuffix.get(suffixCharacter); if (escapable == null) { throw new IllegalArgumentException("Suffix escaped character '" + suffixCharacter + "' can not be escaped"); } return escapable; } }