Java tutorial
/* * Copyright 2013 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.template.soy.msgs.restricted; import com.google.common.collect.ImmutableList; import com.google.template.soy.msgs.SoyMsgBundle; import com.google.template.soy.msgs.restricted.SoyMsgPart.Case; import java.util.Objects; /** * Utility to compact message bundles. * * <p> Important: Only use this class from message plugins! * * <p> This instance will canonicalize different parts of messages to avoid storing the same * objects in memory multiple times, at the expense of static use of memory. * * <p> By using the static factory methods in this class, you ensure that message related objects * are not duplicated in memory, at the cost of having one copy that cannot be garbage collected. * * <p> This saves an enormous amount of memory, especially since in gender/plural messages, there * are many repeated parts. * */ public final class SoyMsgBundleCompactor { /** The default case spec for plural blocks. */ private static final SoyMsgPluralCaseSpec DEFAULT_PLURAL_CASE_SPEC = new SoyMsgPluralCaseSpec("other"); /** The default case spec for select blocks. */ private static final String DEFAULT_SELECT_CASE_SPEC = null; private CompactInterner interner = new CompactInterner(); /** * Returns a more memory-efficient version of the internal message bundle. * * <p> Only enough information is retained for rendering; not enough for message extraction. * As a side effect, this SoyMsgBundleCompactor instance will also retain references to parts * of the messages in order to reuse identical objects. */ public SoyMsgBundle compact(SoyMsgBundle input) { ImmutableList.Builder<SoyMsg> builder = ImmutableList.builder(); for (SoyMsg msg : input) { ImmutableList<SoyMsgPart> parts = compactParts(msg.getParts()); builder.add(new SoyMsg(msg.getId(), msg.getLocaleString(), MsgPartUtils.hasPlrselPart(parts), parts)); } return new RenderOnlySoyMsgBundleImpl(input.getLocaleString(), builder.build()); } /** * Compacts a set of message parts. */ private ImmutableList<SoyMsgPart> compactParts(ImmutableList<SoyMsgPart> parts) { ImmutableList.Builder<SoyMsgPart> builder = ImmutableList.builder(); for (SoyMsgPart part : parts) { builder.add(compactPart(part)); } return builder.build(); } /** * Compacts a single message part. * * If the part is a plural/select part, it might be expanded into multiple parts. */ private SoyMsgPart compactPart(SoyMsgPart part) { if (part instanceof SoyMsgPluralPart) { part = compactPlural((SoyMsgPluralPart) part); } else if (part instanceof SoyMsgSelectPart) { part = compactSelect((SoyMsgSelectPart) part); } // Now intern the message part. return intern(part); } private SoyMsgPart compactSelect(SoyMsgSelectPart select) { // TODO: Turn into a non-select message if there's only one unique case. // Select variable names tend to be repeated across many templates, like "gender". return new SoyMsgSelectPart(intern(select.getSelectVarName()), compactCases(select.getCases(), DEFAULT_SELECT_CASE_SPEC)); } private SoyMsgPart compactPlural(SoyMsgPluralPart plural) { // Plural variable names tend to be repeated across templates, such as "count". return new SoyMsgPluralPart(intern(plural.getPluralVarName()), plural.getOffset(), compactCases(plural.getCases(), DEFAULT_PLURAL_CASE_SPEC)); } /** * Recursively compacts plural/select cases. * * This will attempt to remove unnecessary cases that can easily fall back to the default. * * @param cases Mapping (as pairs) from case spec to the message parts for that case. * @param defaultCaseSpec The default or "other" case specification value. */ private <T> ImmutableList<Case<T>> compactCases(ImmutableList<Case<T>> cases, T defaultCaseSpec) { // Determine the fallback/other case value. ImmutableList<SoyMsgPart> defaultValue = null; for (Case<T> caseAndValue : cases) { if (Objects.equals(caseAndValue.spec(), defaultCaseSpec)) { defaultValue = caseAndValue.parts(); break; } } ImmutableList.Builder<Case<T>> builder = ImmutableList.builder(); for (Case<T> caseAndValue : cases) { // See if this case is the same as the default/other case, but isn't itself the default/other // case, and can be pruned. if (defaultValue != null && !Objects.equals(caseAndValue.spec(), defaultCaseSpec) && defaultValue.equals(caseAndValue.parts())) { continue; } // Intern the case value, since they tend to be very common among templates. For select, // they tend to be strings like "male" or "female", and for plurals, it tends to be one // of the few in the enum. builder.add(Case.create(caseAndValue.spec() != null ? intern(caseAndValue.spec()) : null, compactParts(caseAndValue.parts()))); } return builder.build(); } /** * Returns a possibly canonicalized version of the input. This causes a permanent reference to * the input. */ private <T> T intern(T input) { return interner.intern(input); } }