001/*
002 * Copyright (C) 2009 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;
018import static java.util.Collections.max;
019
020import com.google.common.annotations.GwtCompatible;
021import com.google.common.annotations.VisibleForTesting;
022import java.util.Map;
023
024/**
025 * An implementation-specific parameter class suitable for initializing {@link
026 * ArrayBasedCharEscaper} or {@link ArrayBasedUnicodeEscaper} instances. This class should be used
027 * when more than one escaper is created using the same character replacement mapping to allow the
028 * underlying (implementation specific) data structures to be shared.
029 *
030 * <p>The size of the data structure used by ArrayBasedCharEscaper and ArrayBasedUnicodeEscaper is
031 * proportional to the highest valued character that has a replacement. For example a replacement
032 * map containing the single character '{@literal \}u1000' will require approximately 16K of memory.
033 * As such sharing this data structure between escaper instances is the primary goal of this class.
034 *
035 * @author David Beaumont
036 * @since 15.0
037 */
038@GwtCompatible
039public final class ArrayBasedEscaperMap {
040  /**
041   * Returns a new ArrayBasedEscaperMap for creating ArrayBasedCharEscaper or
042   * ArrayBasedUnicodeEscaper instances.
043   *
044   * @param replacements a map of characters to their escaped representations
045   */
046  public static ArrayBasedEscaperMap create(Map<Character, String> replacements) {
047    return new ArrayBasedEscaperMap(createReplacementArray(replacements));
048  }
049
050  // The underlying replacement array we can share between multiple escaper
051  // instances.
052  private final char[][] replacementArray;
053
054  private ArrayBasedEscaperMap(char[][] replacementArray) {
055    this.replacementArray = replacementArray;
056  }
057
058  // Returns the non-null array of replacements for fast lookup.
059  char[][] getReplacementArray() {
060    return replacementArray;
061  }
062
063  // Creates a replacement array from the given map. The returned array is a
064  // linear lookup table of replacement character sequences indexed by the
065  // original character value.
066  @VisibleForTesting
067  static char[][] createReplacementArray(Map<Character, String> map) {
068    checkNotNull(map); // GWT specific check (do not optimize)
069    if (map.isEmpty()) {
070      return EMPTY_REPLACEMENT_ARRAY;
071    }
072    char max = max(map.keySet());
073    char[][] replacements = new char[max + 1][];
074    for (Character c : map.keySet()) {
075      replacements[c] = map.get(c).toCharArray();
076    }
077    return replacements;
078  }
079
080  // Immutable empty array for when there are no replacements.
081  private static final char[][] EMPTY_REPLACEMENT_ARRAY = new char[0][0];
082}