// Copyright (C) 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.
package com.google.caja.parser;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Encapsulation of the ordered collection of child nodes of a
* {@link com.google.caja.parser.ParseTreeNode}.
*
* @author ihab.awad@gmail.com
*/
final class ChildNodes<T extends ParseTreeNode> implements Serializable {
private static final long serialVersionUID = -3349416361229204091L;
private boolean immutable = false;
public boolean makeImmutable() {
boolean wasMadeImmutable = true;
if (backingList != null) {
for (ParseTreeNode n : backingList) {
wasMadeImmutable = wasMadeImmutable && n.makeImmutable();
}
}
return immutable = wasMadeImmutable;
}
/**
* The actual storage of collection elements. Constructed lazily in case it
* is never used.
*/
private List<T> backingList;
/**
* The class of the collection elements. Used to implement runtime
* type safety of the collection.
*/
private final Class<? extends T> elementClass;
/**
* The facet of this collection supporting mutations to the collection.
*/
private List<T> mutableFacet;
/**
* The facet of this collection through which mutations are not allowed.
*/
private List<T> immutableFacet;
/**
* The implementation of the mutable facet, which checks that newly added
* elements are of type (erasure(T)) which is correct as long as
* ParseTreeNodes are not parameterized.
*
* <p>We could have made this an anonymous class assigned to 'mutableFacet',
* but implementing it as a named class allows us to instantiate it lazily
* (see getMutableFacet()).
*/
private class MutableFacet extends AbstractList<T> implements Serializable {
private static final long serialVersionUID = 3989291162782482786L;
@Override
public int size() { return getBackingList().size(); }
@Override
public T get(int i) { return getBackingList().get(i); }
@Override
public T set(int i, T element) {
return getBackingList().set(i, elementClass.cast(element));
}
@Override
public void add(int i, T element) {
getBackingList().add(i, elementClass.cast(element));
}
@Override
public T remove(int i) { return getBackingList().remove(i); }
}
/**
* Creates a new ChildNodes.
*
* @param elementClass the class of elements that will be added to the
* collection.
*/
public ChildNodes(Class<? extends T> elementClass) {
assert elementClass.getTypeParameters().length == 0;
this.elementClass = elementClass;
}
/**
* Creates a clone of an existing ChildNodes.
*
* @param source a ChildNodes object to copy.
*/
public ChildNodes(ChildNodes<? extends T> source) {
this.backingList = new ArrayList<T>(source.backingList);
this.elementClass = source.elementClass;
}
/**
* @return the class of elements that this ChildNodes can contain.
*/
public Class<? extends T> getElementClass() { return elementClass; }
/**
* @return a List interface to this collection that supports mutations. This
* interface checks any insertions at runtime to ensure they are instances
* of {@link #getElementClass()}.
*/
public List<T> getMutableFacet() {
if (immutable) {
throw new UnsupportedOperationException();
}
if (mutableFacet == null) {
mutableFacet = new MutableFacet();
}
return mutableFacet;
}
/**
* @return a List interface to this collection that does not support
* mutations.
*/
public List<T> getImmutableFacet() {
if (immutableFacet == null) {
immutableFacet = Collections.unmodifiableList(getBackingList());
}
return immutableFacet;
}
/**
* Statically cast this ChildNodes object to represent a collection
* containing a subtype of the original. This only succeeds if the dynamic
* type of this ChildNodes is such that the cast is safe.
*
* @param subClass the desired class of the elements of the result.
* @param <SubT> the desired class of the elements of the result.
* @return a narrowed reference to this ChildNodes.
*/
@SuppressWarnings("unchecked")
public <SubT extends T> ChildNodes<? extends SubT> as(Class<SubT> subClass) {
elementClass.asSubclass(subClass);
return (ChildNodes<SubT>) this;
}
// Accessor for 'backingList' to implement lazy construction.
private List<T> getBackingList() {
if (backingList == null) {
backingList = new ArrayList<T>();
}
return backingList;
}
}