com.google.template.soy.msgs.restricted.RenderOnlySoyMsgBundleImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.google.template.soy.msgs.restricted.RenderOnlySoyMsgBundleImpl.java

Source

/*
 * 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.template.soy.msgs.SoyMsgBundle;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.SortedMap;

import javax.annotation.Nullable;

/**
 * Represents all renderable messages in a locale.
 *
 * <p> This saves significant memory from the normal SoyMsgBundleImpl, but doesn't store details
 * like message descriptions. This also has small runtime performance penalties, such as using
 * binary search instead of hash tables, constructing wrapper objects on the fly, and computing
 * properties of the message instead of storing them.
 *
 */
final class RenderOnlySoyMsgBundleImpl implements SoyMsgBundle {

    /** The language/locale string of this bundle's messages. */
    private final String localeString;

    /**
     * Sorted array of message ID's that can be binary searched.
     *
     * Importantly, this doesn't use any generic List type, to avoid wrapper Long objects.
     */
    private final long[] idArray;

    /**
     * Array containing either SoyMsgPart or ImmutableList<SoyMsgPart> in the same order as idArray.
     */
    private final Object[] valueArray;

    /**
     * Constructs a map of render-only soy messages. This implementation saves memory but doesn't
     * store all fields necessary during extraction.
     *
     * @param localeString The language/locale string of this bundle of messages, or null if unknown.
     *     Should only be null for bundles newly extracted from source files. Should always be set
     *     for bundles parsed from message files/resources.
     * @param msgs The list of messages. List order will become the iteration order. Duplicate
     *     message ID's are not permitted.
     */
    public RenderOnlySoyMsgBundleImpl(@Nullable String localeString, Iterable<SoyMsg> msgs) {

        this.localeString = localeString;

        // First, build a sorted map from message ID to the message representation.
        SortedMap<Long, Object> partsMap = Maps.newTreeMap();
        for (SoyMsg msg : msgs) {
            checkArgument(Objects.equals(msg.getLocaleString(), localeString));
            checkArgument(msg.getAltId() < 0, "RenderOnlySoyMsgBundleImpl doesn't support alternate ID's.");
            long msgId = msg.getId();
            checkArgument(!partsMap.containsKey(msgId),
                    "Duplicate messages are not permitted in the render-only impl.");

            List<SoyMsgPart> parts = msg.getParts();
            checkArgument(MsgPartUtils.hasPlrselPart(parts) == msg.isPlrselMsg(),
                    "Message's plural/select status is inconsistent -- internal compiler bug.");
            // Save memory: don't store the list if there's only one item.
            if (parts.size() == 1) {
                partsMap.put(msgId, parts.get(0));
            } else {
                partsMap.put(msgId, ImmutableList.copyOf(parts));
            }
        }

        // Using parallel long[] and Object[] arrays saves memory versus using a Map, because it avoids
        // having to wrap the longs in a new Long(), and avoids wrapping the key/value pair in an
        // Entry. Also, using a sorted array utilizes memory better, since unlike a hash table, you
        // need neither a linked list nor empty spaces in the hash table.
        idArray = new long[partsMap.size()];
        valueArray = new Object[partsMap.size()];

        // Build the arrays in the same order as the sorted map. Note we can't use toArray() since it
        // won't create a primitive long[] (only Long wrappers).
        int index = 0;
        for (Map.Entry<Long, Object> entry : partsMap.entrySet()) {
            idArray[index] = entry.getKey();
            valueArray[index] = entry.getValue();
            index++;
        }
        checkState(index == partsMap.size());
    }

    /**
     * Brings a message back to life from only its ID and parts.
     */
    @SuppressWarnings("unchecked") // The constructor guarantees the type of ImmutableList.
    private SoyMsg resurrectMsg(long id, Object value) {
        // Remember: If there's only one message part, we don't store the list wrapper.
        ImmutableList<SoyMsgPart> parts = (value instanceof SoyMsgPart) ? ImmutableList.of((SoyMsgPart) value)
                : ((ImmutableList<SoyMsgPart>) value);
        return new SoyMsg(id, localeString, MsgPartUtils.hasPlrselPart(parts), parts);
    }

    @Override
    public String getLocaleString() {
        return localeString;
    }

    @Override
    public SoyMsg getMsg(long msgId) {
        int index = Arrays.binarySearch(idArray, msgId);
        return index >= 0 ? resurrectMsg(msgId, valueArray[index]) : null;
    }

    @Override
    public int getNumMsgs() {
        return idArray.length;
    }

    @Override
    public Iterator<SoyMsg> iterator() {
        return new Iterator<SoyMsg>() {
            int index = 0;

            @Override
            public boolean hasNext() {
                return index < idArray.length;
            }

            @Override
            public SoyMsg next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                SoyMsg result = resurrectMsg(idArray[index], valueArray[index]);
                index++;
                return result;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException("Iterator is immutable");
            }
        };
    }
}