001/* 002 * Copyright (C) 2006 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.escape; 018 019import static com.google.common.base.Preconditions.checkNotNull; 020 021import com.google.common.annotations.Beta; 022import com.google.common.annotations.GwtCompatible; 023 024import java.util.HashMap; 025import java.util.Map; 026 027/** 028 * Simple helper class to build a "sparse" array of objects based on the indexes that were added to 029 * it. The array will be from 0 to the maximum index given. All non-set indexes will contain null 030 * (so it's not really a sparse array, just a pseudo sparse array). The builder can also return a 031 * CharEscaper based on the generated array. 032 * 033 * @author Sven Mawson 034 * @since 15.0 035 */ 036@Beta 037@GwtCompatible 038public final class CharEscaperBuilder { 039 /** 040 * Simple decorator that turns an array of replacement char[]s into a CharEscaper, this results in 041 * a very fast escape method. 042 */ 043 private static class CharArrayDecorator extends CharEscaper { 044 private final char[][] replacements; 045 private final int replaceLength; 046 047 CharArrayDecorator(char[][] replacements) { 048 this.replacements = replacements; 049 this.replaceLength = replacements.length; 050 } 051 052 /* 053 * Overriding escape method to be slightly faster for this decorator. We test the replacements 054 * array directly, saving a method call. 055 */ 056 @Override public String escape(String s) { 057 int slen = s.length(); 058 for (int index = 0; index < slen; index++) { 059 char c = s.charAt(index); 060 if (c < replacements.length && replacements[c] != null) { 061 return escapeSlow(s, index); 062 } 063 } 064 return s; 065 } 066 067 @Override protected char[] escape(char c) { 068 return c < replaceLength ? replacements[c] : null; 069 } 070 } 071 072 // Replacement mappings. 073 private final Map<Character, String> map; 074 075 // The highest index we've seen so far. 076 private int max = -1; 077 078 /** 079 * Construct a new sparse array builder. 080 */ 081 public CharEscaperBuilder() { 082 this.map = new HashMap<Character, String>(); 083 } 084 085 /** 086 * Add a new mapping from an index to an object to the escaping. 087 */ 088 public CharEscaperBuilder addEscape(char c, String r) { 089 map.put(c, checkNotNull(r)); 090 if (c > max) { 091 max = c; 092 } 093 return this; 094 } 095 096 /** 097 * Add multiple mappings at once for a particular index. 098 */ 099 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}