/* * 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.template.soy.data; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.template.soy.data.restricted.BooleanData; import com.google.template.soy.data.restricted.CollectionData; import com.google.template.soy.data.restricted.FloatData; import com.google.template.soy.data.restricted.IntegerData; import com.google.template.soy.data.restricted.StringData; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.annotation.Nonnull; /** * A list data node in a Soy data tree. * */ public final class SoyListData extends CollectionData implements Iterable<SoyData>, SoyList { /** The underlying list. */ private final List<SoyData> list; public SoyListData() { list = Lists.newArrayList(); } /** * Constructor that initializes this SoyListData from an existing list. * * @param data The initial data in an existing Iterable. */ public SoyListData(Iterable<?> data) { this(); add(data); } /** * Constructor that initializes this SoyListData with the given elements. * * @param values The initial data to add. */ public SoyListData(Object... values) { this(Arrays.asList(values)); } /** * Important: Please treat this method as superpackage-private. Do not call this method from * outside the 'tofu' and 'data' packages. * * <p>Returns a view of this SoyListData object as a List. */ public List<SoyData> asList() { return Collections.unmodifiableList(list); } /** * {@inheritDoc} * * <p>This method should only be used for debugging purposes. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); try { render(sb); } catch (IOException e) { throw new RuntimeException(e); // impossible } return sb.toString(); } @Override public void render(Appendable appendable) throws IOException { appendable.append("["); int size = list.size(); if (size != 0) { list.get(0).render(appendable); for (int i = 1; i < size; i++) { appendable.append(", "); list.get(i).render(appendable); } } appendable.append("]"); } /** * {@inheritDoc} * * <p>A list is always truthy. */ @Override public boolean coerceToBoolean() { return true; } @Override public String coerceToString() { return toString(); } @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object other) { return this == other; // fall back to object equality } /** * Gets the length of this list. * * @return The length of this list. */ @Override public int length() { return list.size(); } @Override public Iterator<SoyData> iterator() { return Collections.unmodifiableList(list).iterator(); } // ------------ add() ------------ /** * Private helper shared by constructor SoyListData(Iterable) and add(Object...). * * @param data The data to add. */ private void add(Iterable<?> data) { for (Object el : data) { try { add(SoyData.createFromExistingData(el)); } catch (SoyDataException sde) { sde.prependIndexToDataPath(list.size()); throw sde; } } } /** * Convenience function to add multiple values in one call. * * @param values The data to add. */ public void add(Object... values) { add(Arrays.asList(values)); } /** * Adds a data value. * * @param value The data to add. */ public void add(SoyData value) { list.add(ensureValidValue(value)); } /** * Adds a data value. * * @param value The data to add. */ public void add(boolean value) { add(BooleanData.forValue(value)); } /** * Adds a data value. * * @param value The data to add. */ public void add(int value) { add(IntegerData.forValue(value)); } /** * Adds a data value. * * @param value The data to add. */ public void add(long value) { add(IntegerData.forValue(value)); } /** * Adds a data value. * * @param value The data to add. */ public void add(double value) { add(FloatData.forValue(value)); } /** * Adds a data value. * * @param value The data to add. */ public void add(String value) { add(StringData.forValue(value)); } // ------------ set() ------------ /** * Sets a data value at a given index. * * @param index The index. * @param value The data to set. */ public void set(int index, SoyData value) { if (index == list.size()) { list.add(ensureValidValue(value)); } else { list.set(index, ensureValidValue(value)); } } /** * Sets a data value at a given index. * * @param index The index. * @param value The data to set. */ public void set(int index, boolean value) { set(index, BooleanData.forValue(value)); } /** * Sets a data value at a given index. * * @param index The index. * @param value The data to set. */ public void set(int index, int value) { set(index, IntegerData.forValue(value)); } /** * Sets a data value at a given index. * * @param index The index. * @param value The data to set. */ public void set(int index, double value) { set(index, FloatData.forValue(value)); } /** * Sets a data value at a given index. * * @param index The index. * @param value The data to set. */ public void set(int index, String value) { set(index, StringData.forValue(value)); } // ------------ remove() ------------ /** * Removes the data value at a given index. * * @param index The index. */ public void remove(int index) { list.remove(index); } // ------------ get*() ------------ /** * Gets the data value at a given index. * * @param index The index. * @return The data at the given index, or null of the index is undefined. */ @Override public SoyData get(int index) { try { return list.get(index); } catch (IndexOutOfBoundsException ioobe) { return null; } } /** * Precondition: The specified index contains a SoyMapData object. Gets the SoyMapData at the * given index. * * @param index The index. * @return The SoyMapData at the given index, or null of the index is undefined. */ public SoyMapData getMapData(int index) { return (SoyMapData) get(index); } /** * Precondition: The specified index contains a SoyListData object. Gets the SoyListData at the * given index. * * @param index The index. * @return The SoyListData at the given index, or null of the index is undefined. */ public SoyListData getListData(int index) { return (SoyListData) get(index); } /** * Precondition: The specified index contains a boolean. Gets the boolean at the given index. * * @param index The index. * @return The boolean at the given index, or null of the index is undefined. */ public boolean getBoolean(int index) { return get(index).booleanValue(); } /** * Precondition: The specified index contains an integer. Gets the integer at the given index. * * @param index The index. * @return The integer at the given index, or null of the index is undefined. */ public int getInteger(int index) { return get(index).integerValue(); } /** * Precondition: The specified index contains a long. Gets the long at the given index. * * @param index The index. * @return The long at the given index, or null of the index is undefined. */ public long getLong(int index) { return get(index).longValue(); } /** * Precondition: The specified index contains a float. Gets the float at the given index. * * @param index The index. * @return The float at the given index, or null of the index is undefined. */ public double getFloat(int index) { return get(index).floatValue(); } /** * Precondition: The specified index contains a string. Gets the string at the given index. * * @param index The index. * @return The string at the given index, or null of the index is undefined. */ public String getString(int index) { return get(index).stringValue(); } // ----------------------------------------------------------------------------------------------- // Superpackage-private methods. /** * Important: Do not use outside of Soy code (treat as superpackage-private). * * <p>Puts data into this data object at the specified key. * * @param key An individual key. * @param value The data to put at the specified key. */ @Override public void putSingle(String key, SoyData value) { set(Integer.parseInt(key), value); } /** * Important: Do not use outside of Soy code (treat as superpackage-private). * * <p>Removes the data at the specified key. * * @param key An individual key. */ @Override public void removeSingle(String key) { remove(Integer.parseInt(key)); } /** * Important: Do not use outside of Soy code (treat as superpackage-private). * * <p>Gets the data at the specified key. * * @param key An individual key. * @return The data at the specified key, or null if the key is not defined. */ @Override public SoyData getSingle(String key) { return get(Integer.parseInt(key)); } // ----------------------------------------------------------------------------------------------- // SoyList. @Override @Nonnull public List<? extends SoyValueProvider> asJavaList() { return asList(); } @Override @Nonnull public List<? extends SoyValue> asResolvedJavaList() { return asList(); } @Override public SoyValueProvider getProvider(int index) { return get(index); } // ----------------------------------------------------------------------------------------------- // SoyMap. @Override public int getItemCnt() { return length(); } @Override @Nonnull public Iterable<? extends SoyValue> getItemKeys() { ImmutableList.Builder<IntegerData> indexesBuilder = ImmutableList.builder(); for (int i = 0, n = length(); i < n; i++) { indexesBuilder.add(IntegerData.forValue(i)); } return indexesBuilder.build(); } @Override public boolean hasItem(SoyValue key) { int index = getIntegerIndex(key); return 0 <= index && index < length(); } @Override public SoyValue getItem(SoyValue key) { return get(getIntegerIndex(key)); } @Override public SoyValueProvider getItemProvider(SoyValue key) { return get(getIntegerIndex(key)); } /** * Gets the integer index out of a SoyValue key, or throws SoyDataException if the key is not an * integer. * * @param key The SoyValue key. * @return The index. */ private int getIntegerIndex(SoyValue key) { try { return ((IntegerData) key).integerValue(); } catch (ClassCastException cce) { try { // TODO: Remove this old bad behavior after existing code is compliant. return Integer.parseInt(key.coerceToString()); } catch (NumberFormatException nfe) { throw new SoyDataException( "SoyList accessed with non-integer key (got key type " + key.getClass().getName() + ")."); } } } }