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