1/* 2 * Copyright (C) 2006 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.common.escape; 18 19import static com.google.common.base.Preconditions.checkNotNull; 20 21import com.google.common.annotations.Beta; 22import com.google.common.annotations.GwtCompatible; 23 24import java.util.HashMap; 25import java.util.Map; 26 27/** 28 * Simple helper class to build a "sparse" array of objects based on the indexes that were added to 29 * it. The array will be from 0 to the maximum index given. All non-set indexes will contain null 30 * (so it's not really a sparse array, just a pseudo sparse array). The builder can also return a 31 * CharEscaper based on the generated array. 32 * 33 * @author Sven Mawson 34 * @since 15.0 35 */ 36@Beta 37@GwtCompatible 38public final class CharEscaperBuilder { 39 /** 40 * Simple decorator that turns an array of replacement char[]s into a CharEscaper, this results in 41 * a very fast escape method. 42 */ 43 private static class CharArrayDecorator extends CharEscaper { 44 private final char[][] replacements; 45 private final int replaceLength; 46 47 CharArrayDecorator(char[][] replacements) { 48 this.replacements = replacements; 49 this.replaceLength = replacements.length; 50 } 51 52 /* 53 * Overriding escape method to be slightly faster for this decorator. We test the replacements 54 * array directly, saving a method call. 55 */ 56 @Override public String escape(String s) { 57 int slen = s.length(); 58 for (int index = 0; index < slen; index++) { 59 char c = s.charAt(index); 60 if (c < replacements.length && replacements[c] != null) { 61 return escapeSlow(s, index); 62 } 63 } 64 return s; 65 } 66 67 @Override protected char[] escape(char c) { 68 return c < replaceLength ? replacements[c] : null; 69 } 70 } 71 72 // Replacement mappings. 73 private final Map<Character, String> map; 74 75 // The highest index we've seen so far. 76 private int max = -1; 77 78 /** 79 * Construct a new sparse array builder. 80 */ 81 public CharEscaperBuilder() { 82 this.map = new HashMap<Character, String>(); 83 } 84 85 /** 86 * Add a new mapping from an index to an object to the escaping. 87 */ 88 public CharEscaperBuilder addEscape(char c, String r) { 89 map.put(c, checkNotNull(r)); 90 if (c > max) { 91 max = c; 92 } 93 return this; 94 } 95 96 /** 97 * Add multiple mappings at once for a particular index. 98 */ 99 public CharEscaperBuilder addEscapes(char[] cs, String r) { 100 checkNotNull(r); 101 for (char c : cs) { 102 addEscape(c, r); 103 } 104 return this; 105 } 106 107 /** 108 * Convert this builder into an array of char[]s where the maximum index is the value of the 109 * highest character that has been seen. The array will be sparse in the sense that any unseen 110 * index will default to null. 111 * 112 * @return a "sparse" array that holds the replacement mappings. 113 */ 114 public char[][] toArray() { 115 char[][] result = new char[max + 1][]; 116 for (Map.Entry<Character, String> entry : map.entrySet()) { 117 result[entry.getKey()] = entry.getValue().toCharArray(); 118 } 119 return result; 120 } 121 122 /** 123 * Convert this builder into a char escaper which is just a decorator around the underlying array 124 * of replacement char[]s. 125 * 126 * @return an escaper that escapes based on the underlying array. 127 */ 128 public Escaper toEscaper() { 129 return new CharArrayDecorator(toArray()); 130 } 131} 132