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 
17 package com.google.common.escape;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import com.google.common.annotations.Beta;
22 import com.google.common.annotations.GwtCompatible;
23 
24 import java.util.HashMap;
25 import 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
38 public 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 
CharArrayDecorator(char[][] replacements)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      */
escape(String s)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 
escape(char c)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    */
CharEscaperBuilder()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    */
addEscape(char c, String r)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    */
addEscapes(char[] cs, String r)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    */
toArray()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    */
toEscaper()128   public Escaper toEscaper() {
129     return new CharArrayDecorator(toArray());
130   }
131 }
132