com.google.common.css.MinimalSubstitutionMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-stylesheets Show documentation
Show all versions of closure-stylesheets Show documentation
Closure Stylesheets is an extension to CSS that adds variables,
functions,
conditionals, and mixins to standard CSS. The tool also supports
minification, linting, RTL flipping, and CSS class renaming.
The newest version!
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.css;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
/**
* MinimalSubstitutionMap is a SubstitutionMap that renames CSS classes to the
* shortest string possible.
*
* @author [email protected] (Michael Bolin)
*/
public class MinimalSubstitutionMap implements SubstitutionMap {
/** Possible first chars in a CSS class name */
private static final char[] START_CHARS = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
};
/** Possible non-first chars in a CSS class name */
private static final char[] CHARS = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
};
/**
* Last value used with toShortString().
*/
private int lastIndex;
/**
* Characters that can be used at the start of a CSS class name.
*/
private final char[] startChars;
/**
* Characters that can be used in a CSS class name (though not necessarily as
* the first character).
*/
private final char[] chars;
/**
* Number of startChars.
*/
private final int startCharsRadix;
/**
* Number of chars.
*/
private final int charsRadix;
/**
* Value equal to Math.log(charsRadix). Stored as a field so it does not need
* to be recomputed each time toShortString() is invoked.
*/
private final double logCharsRadix;
/**
* Map of CSS classes that were renamed. Keys are original class names and
* values are their renamed equivalents.
*/
private final Map renamedCssClasses;
/**
* A set of CSS class names that may not be output from this substitution map.
*/
private final Set outputValueBlacklist;
public MinimalSubstitutionMap() {
this(ImmutableSet.of());
}
/**
* @param outputValueBlacklist A set of CSS class names that may not be
* returned as the output from a substitution lookup.
*/
public MinimalSubstitutionMap(Set outputValueBlacklist) {
this(START_CHARS, CHARS, outputValueBlacklist);
}
/**
* Creates a new MinimalSubstitutionMap that generates CSS class names from
* the specified set of characters.
* @param startChars Possible values for the first character of a CSS class
* name.
* @param chars Possible values for the characters other than the first
* character in a CSS class name.
*/
@VisibleForTesting
MinimalSubstitutionMap(char[] startChars, char[] chars) {
this(startChars, chars, ImmutableSet.of());
}
/**
* Creates a new MinimalSubstitutionMap that generates CSS class names from
* the specified set of characters.
* @param startChars Possible values for the first character of a CSS class
* name.
* @param chars Possible values for the characters other than the first
* character in a CSS class name.
* @param outputValueBlacklist A set of CSS class names that may not be
* returned as the output from a substitution lookup.
*/
@VisibleForTesting
MinimalSubstitutionMap(
char[] startChars, char[] chars, Set outputValueBlacklist) {
this.lastIndex = 0;
this.startChars = Arrays.copyOf(startChars, startChars.length);
this.startCharsRadix = this.startChars.length;
this.chars = Arrays.copyOf(chars, chars.length);
this.charsRadix = this.chars.length;
this.logCharsRadix = Math.log(charsRadix);
this.renamedCssClasses = Maps.newHashMap();
this.outputValueBlacklist =
ImmutableSet.copyOf(Preconditions.checkNotNull(outputValueBlacklist));
}
/** {@inheritDoc} */
@Override
public String get(String key) {
String value = renamedCssClasses.get(key);
if (value == null) {
do {
value = toShortString(lastIndex++);
} while (this.outputValueBlacklist.contains(value));
renamedCssClasses.put(key, value);
}
return value;
}
/**
* Converts a 32-bit integer to a unique short string whose first character
* is in {@link #START_CHARS} and whose subsequent characters, if any, are
* in {@link #CHARS}. The result is 1-6 characters in length.
* @param index The index into the enumeration of possible CSS class names
* given the set of valid CSS characters in this class.
* @return The CSS class name that corresponds to the index of the
* enumeration.
*/
@VisibleForTesting
String toShortString(int index) {
// Given the number of non-start characters, C, then for each start
// character, S, there will be:
// 1 one-letter CSS class name that starts with S
// C two-letter CSS class names that start with S
// C^2 three-letter CSS class names that start with S
// C^3 four-letter CSS class names that start with S
// and so on...
//
// That means that the number of non-start characters, n, in terms of i is
// defined as the greatest value of n that satisfies the following:
//
// 1 + C + C^2 + ... + C^(n - 1) <= i
//
// Substituting (C^n - 1) / (C - 1) for the geometric series, we get:
//
// (C^n - 1) / (C - 1) <= i
// (C^n - 1) <= i * (C - 1)
// C^n <= i * (C - 1) + 1
// log C^n <= log (i * (C - 1) + 1)
// n log C <= log (i * (C - 1) + 1)
// n <= log (i * (C - 1) + 1) / log C
//
// Because we are looking for the largest value of n that satisfies the
// inequality and we require n to be an integer, n can be expressed as:
//
// n = [[ log (i * (C - 1) + 1) / log C ]]
//
// where [[ x ]] is the greatest integer not exceeding x.
//
// Once n is known, the standard modulo-then-divide approach can be used to
// determine each character that should be appended to s.
int i = index / startCharsRadix;
final int n = (int) (Math.log(i * (charsRadix - 1) + 1) / logCharsRadix);
// The array is 1 more than the number of secondary chars to account for the
// first char.
char[] cssNameChars = new char[n + 1];
cssNameChars[0] = startChars[index % startCharsRadix];
for (int k = 1; k <= n; ++k) {
cssNameChars[k] = chars[i % charsRadix];
i /= charsRadix;
}
return new String(cssNameChars);
}
}