10888a09821a98ac0680fad765217302858e70fa4Paul Duffin/* 20888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Copyright (C) 2006 The Guava Authors 30888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 40888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Licensed under the Apache License, Version 2.0 (the "License"); 50888a09821a98ac0680fad765217302858e70fa4Paul Duffin * you may not use this file except in compliance with the License. 60888a09821a98ac0680fad765217302858e70fa4Paul Duffin * You may obtain a copy of the License at 70888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 80888a09821a98ac0680fad765217302858e70fa4Paul Duffin * http://www.apache.org/licenses/LICENSE-2.0 90888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 100888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Unless required by applicable law or agreed to in writing, software 110888a09821a98ac0680fad765217302858e70fa4Paul Duffin * distributed under the License is distributed on an "AS IS" BASIS, 120888a09821a98ac0680fad765217302858e70fa4Paul Duffin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130888a09821a98ac0680fad765217302858e70fa4Paul Duffin * See the License for the specific language governing permissions and 140888a09821a98ac0680fad765217302858e70fa4Paul Duffin * limitations under the License. 150888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 160888a09821a98ac0680fad765217302858e70fa4Paul Duffin 170888a09821a98ac0680fad765217302858e70fa4Paul Duffinpackage com.google.common.escape; 180888a09821a98ac0680fad765217302858e70fa4Paul Duffin 190888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport static com.google.common.base.Preconditions.checkNotNull; 200888a09821a98ac0680fad765217302858e70fa4Paul Duffin 210888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport com.google.common.annotations.Beta; 220888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport com.google.common.annotations.GwtCompatible; 230888a09821a98ac0680fad765217302858e70fa4Paul Duffin 240888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport java.util.HashMap; 250888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport java.util.Map; 260888a09821a98ac0680fad765217302858e70fa4Paul Duffin 270888a09821a98ac0680fad765217302858e70fa4Paul Duffin/** 280888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Simple helper class to build a "sparse" array of objects based on the indexes that were added to 290888a09821a98ac0680fad765217302858e70fa4Paul Duffin * it. The array will be from 0 to the maximum index given. All non-set indexes will contain null 300888a09821a98ac0680fad765217302858e70fa4Paul Duffin * (so it's not really a sparse array, just a pseudo sparse array). The builder can also return a 310888a09821a98ac0680fad765217302858e70fa4Paul Duffin * CharEscaper based on the generated array. 320888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 330888a09821a98ac0680fad765217302858e70fa4Paul Duffin * @author Sven Mawson 340888a09821a98ac0680fad765217302858e70fa4Paul Duffin * @since 15.0 350888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 360888a09821a98ac0680fad765217302858e70fa4Paul Duffin@Beta 370888a09821a98ac0680fad765217302858e70fa4Paul Duffin@GwtCompatible 380888a09821a98ac0680fad765217302858e70fa4Paul Duffinpublic final class CharEscaperBuilder { 390888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 400888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Simple decorator that turns an array of replacement char[]s into a CharEscaper, this results in 410888a09821a98ac0680fad765217302858e70fa4Paul Duffin * a very fast escape method. 420888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 430888a09821a98ac0680fad765217302858e70fa4Paul Duffin private static class CharArrayDecorator extends CharEscaper { 440888a09821a98ac0680fad765217302858e70fa4Paul Duffin private final char[][] replacements; 450888a09821a98ac0680fad765217302858e70fa4Paul Duffin private final int replaceLength; 460888a09821a98ac0680fad765217302858e70fa4Paul Duffin 470888a09821a98ac0680fad765217302858e70fa4Paul Duffin CharArrayDecorator(char[][] replacements) { 480888a09821a98ac0680fad765217302858e70fa4Paul Duffin this.replacements = replacements; 490888a09821a98ac0680fad765217302858e70fa4Paul Duffin this.replaceLength = replacements.length; 500888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 510888a09821a98ac0680fad765217302858e70fa4Paul Duffin 520888a09821a98ac0680fad765217302858e70fa4Paul Duffin /* 530888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Overriding escape method to be slightly faster for this decorator. We test the replacements 540888a09821a98ac0680fad765217302858e70fa4Paul Duffin * array directly, saving a method call. 550888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 560888a09821a98ac0680fad765217302858e70fa4Paul Duffin @Override public String escape(String s) { 570888a09821a98ac0680fad765217302858e70fa4Paul Duffin int slen = s.length(); 580888a09821a98ac0680fad765217302858e70fa4Paul Duffin for (int index = 0; index < slen; index++) { 590888a09821a98ac0680fad765217302858e70fa4Paul Duffin char c = s.charAt(index); 600888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (c < replacements.length && replacements[c] != null) { 610888a09821a98ac0680fad765217302858e70fa4Paul Duffin return escapeSlow(s, index); 620888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 630888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 640888a09821a98ac0680fad765217302858e70fa4Paul Duffin return s; 650888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 660888a09821a98ac0680fad765217302858e70fa4Paul Duffin 670888a09821a98ac0680fad765217302858e70fa4Paul Duffin @Override protected char[] escape(char c) { 680888a09821a98ac0680fad765217302858e70fa4Paul Duffin return c < replaceLength ? replacements[c] : null; 690888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 700888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 710888a09821a98ac0680fad765217302858e70fa4Paul Duffin 720888a09821a98ac0680fad765217302858e70fa4Paul Duffin // Replacement mappings. 730888a09821a98ac0680fad765217302858e70fa4Paul Duffin private final Map<Character, String> map; 740888a09821a98ac0680fad765217302858e70fa4Paul Duffin 750888a09821a98ac0680fad765217302858e70fa4Paul Duffin // The highest index we've seen so far. 760888a09821a98ac0680fad765217302858e70fa4Paul Duffin private int max = -1; 770888a09821a98ac0680fad765217302858e70fa4Paul Duffin 780888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 790888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Construct a new sparse array builder. 800888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 810888a09821a98ac0680fad765217302858e70fa4Paul Duffin public CharEscaperBuilder() { 820888a09821a98ac0680fad765217302858e70fa4Paul Duffin this.map = new HashMap<Character, String>(); 830888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 840888a09821a98ac0680fad765217302858e70fa4Paul Duffin 850888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 860888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Add a new mapping from an index to an object to the escaping. 870888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 880888a09821a98ac0680fad765217302858e70fa4Paul Duffin public CharEscaperBuilder addEscape(char c, String r) { 890888a09821a98ac0680fad765217302858e70fa4Paul Duffin map.put(c, checkNotNull(r)); 900888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (c > max) { 910888a09821a98ac0680fad765217302858e70fa4Paul Duffin max = c; 920888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 930888a09821a98ac0680fad765217302858e70fa4Paul Duffin return this; 940888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 950888a09821a98ac0680fad765217302858e70fa4Paul Duffin 960888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 970888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Add multiple mappings at once for a particular index. 980888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 990888a09821a98ac0680fad765217302858e70fa4Paul Duffin public CharEscaperBuilder addEscapes(char[] cs, String r) { 1000888a09821a98ac0680fad765217302858e70fa4Paul Duffin checkNotNull(r); 1010888a09821a98ac0680fad765217302858e70fa4Paul Duffin for (char c : cs) { 1020888a09821a98ac0680fad765217302858e70fa4Paul Duffin addEscape(c, r); 1030888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 1040888a09821a98ac0680fad765217302858e70fa4Paul Duffin return this; 1050888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 1060888a09821a98ac0680fad765217302858e70fa4Paul Duffin 1070888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 1080888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Convert this builder into an array of char[]s where the maximum index is the value of the 1090888a09821a98ac0680fad765217302858e70fa4Paul Duffin * highest character that has been seen. The array will be sparse in the sense that any unseen 1100888a09821a98ac0680fad765217302858e70fa4Paul Duffin * index will default to null. 1110888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 1120888a09821a98ac0680fad765217302858e70fa4Paul Duffin * @return a "sparse" array that holds the replacement mappings. 1130888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 1140888a09821a98ac0680fad765217302858e70fa4Paul Duffin public char[][] toArray() { 1150888a09821a98ac0680fad765217302858e70fa4Paul Duffin char[][] result = new char[max + 1][]; 1160888a09821a98ac0680fad765217302858e70fa4Paul Duffin for (Map.Entry<Character, String> entry : map.entrySet()) { 1170888a09821a98ac0680fad765217302858e70fa4Paul Duffin result[entry.getKey()] = entry.getValue().toCharArray(); 1180888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 1190888a09821a98ac0680fad765217302858e70fa4Paul Duffin return result; 1200888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 1210888a09821a98ac0680fad765217302858e70fa4Paul Duffin 1220888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 1230888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Convert this builder into a char escaper which is just a decorator around the underlying array 1240888a09821a98ac0680fad765217302858e70fa4Paul Duffin * of replacement char[]s. 1250888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 1260888a09821a98ac0680fad765217302858e70fa4Paul Duffin * @return an escaper that escapes based on the underlying array. 1270888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 1280888a09821a98ac0680fad765217302858e70fa4Paul Duffin public Escaper toEscaper() { 1290888a09821a98ac0680fad765217302858e70fa4Paul Duffin return new CharArrayDecorator(toArray()); 1300888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 1310888a09821a98ac0680fad765217302858e70fa4Paul Duffin} 132