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 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"); } }; } }