/*
* Copyright 2009 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.
*/
// TODO(bolinfest): Move this to com.google.common.css.compiler.passes.
package com.google.common.css;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.css.MultipleMappingSubstitutionMap.ValueWithMappings;
import java.util.Map;
/**
* A decorator for a {@link SubstitutionMap} that records which values it maps.
*
* @author bolinfest@google.com (Michael Bolin)
*/
public class RecordingSubstitutionMap implements SubstitutionMap.Initializable {
private final SubstitutionMap delegate;
private final Predicate<? super String> shouldRecordMappingForCodeGeneration;
// Use a LinkedHashMap so getMappings() is deterministic.
private final Map<String, String> mappings = Maps.newLinkedHashMap();
/**
* Creates a new instance.
*
* @param map A SubstitutionMap decorated by this class
* @param shouldRecordMappingForCodeGeneration A predicate that returns true if the mapping should
* be recorded for code-generation purposes
* @deprecated Use {@link Builder} instead.
*/
@Deprecated
public RecordingSubstitutionMap(
SubstitutionMap map, Predicate<? super String> shouldRecordMappingForCodeGeneration) {
this.delegate = map;
this.shouldRecordMappingForCodeGeneration =
shouldRecordMappingForCodeGeneration;
}
/**
* {@inheritDoc}
* @throws NullPointerException if key is null.
*/
@Override
public String get(String key) {
Preconditions.checkNotNull(key);
if (!shouldRecordMappingForCodeGeneration.apply(key)) {
return key;
}
if (delegate instanceof MultipleMappingSubstitutionMap) {
// The final value only bears a loose relationship to the mappings.
// For example, PrefixingSubstitutionMap applied to a MinimalSubstitutionMap
// minimizes all components but only prefixes the first.
// We can't memoize the value here, so don't look up in mappings first.
ValueWithMappings valueWithMappings =
((MultipleMappingSubstitutionMap) delegate).getValueWithMappings(key);
mappings.putAll(valueWithMappings.mappings);
return valueWithMappings.value;
} else {
String value = mappings.get(key);
if (value == null) {
value = delegate.get(key);
mappings.put(key, value);
}
return value;
}
}
/**
* @return The recorded mappings in the order they were created. This output may be used with
* {@link OutputRenamingMapFormat#writeRenamingMap}
*/
public Map<String, String> getMappings() {
return ImmutableMap.copyOf(mappings);
}
@Override
public void initializeWithMappings(Map<? extends String, ? extends String> newMappings) {
Preconditions.checkState(mappings.isEmpty());
if (!newMappings.isEmpty()) {
mappings.putAll(newMappings);
((SubstitutionMap.Initializable) delegate).initializeWithMappings(newMappings);
}
}
/** A-la-carte builder. */
public static final class Builder {
private SubstitutionMap delegate = new IdentitySubstitutionMap();
private Predicate<? super String> shouldRecordMappingForCodeGeneration =
Predicates.alwaysTrue();
private Map<String, String> mappings = Maps.newLinkedHashMap();
/** Specifies the underlying map. Multiple calls clobber. */
public Builder withSubstitutionMap(SubstitutionMap d) {
this.delegate = Preconditions.checkNotNull(d);
return this;
}
/**
* True keys that should be treated mapped to themselves instead of passing through Multiple
* calls AND.
*/
public Builder shouldRecordMappingForCodeGeneration(Predicate<? super String> p) {
shouldRecordMappingForCodeGeneration =
Predicates.and(shouldRecordMappingForCodeGeneration, p);
return this;
}
/**
* Specifies mappings to {@linkplain Initializable initialize} the delegate with. Multiple calls
* putAll. This can be used to reconstitute a map that was written out by {@link
* OutputRenamingMapFormat#writeRenamingMap} from the output of {@link
* OutputRenamingMapFormat#readRenamingMap}.
*/
public Builder withMappings(Map<? extends String, ? extends String> m) {
this.mappings.putAll(m);
return this;
}
/** Builds the substitution map based on previous operations on this builder. */
public RecordingSubstitutionMap build() {
// TODO(msamuel): if delegate instanceof MultipleMappingSubstitutionMap
// should this return a RecordingSubstitutionMap that is itself
// a MultipleMappingSubstitutionMap.
RecordingSubstitutionMap built =
new RecordingSubstitutionMap(delegate, shouldRecordMappingForCodeGeneration);
built.initializeWithMappings(mappings);
return built;
}
}
}