/* Copyright (c) 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.gdata.model;
import com.google.gdata.model.ElementMetadata.SingleVirtualElement;
import com.google.gdata.model.ElementMetadata.MultipleVirtualElement;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* An iterator over child elements of an element using the metadata to set the
* iteration order. If no metadata is provided the child elements will be
* treated as if they were all undeclared, and the iteration order will be the
* order they were added to the element.
*
*
*/
class ElementIterator implements Iterator<Element> {
private enum Mode {DECLARED, UNDECLARED, DONE}
private final Element element;
private final ElementMetadata<?, ?> metadata;
private final Map<QName, Object> elements;
private Iterator<ElementKey<?, ?>> metadataIterator;
private Iterator<? extends Element> sublistIterator;
private Iterator<Object> elementIterator;
private Element nextElement;
private Mode mode = Mode.DECLARED;
ElementIterator(Element element, ElementMetadata<?, ?> metadata,
Map<QName, Object> elements) {
this.element = element;
this.metadata = metadata;
this.elements = elements;
this.metadataIterator = (metadata == null) ? null
: metadata.getElements().iterator();
this.elementIterator = (elements == null) ? null
: elements.values().iterator();
nextElement = findNextElement();
}
public boolean hasNext() {
return nextElement != null;
}
public Element next() {
if (nextElement == null) {
throw new NoSuchElementException("No remaining elements");
}
Element retval = nextElement;
nextElement = findNextElement();
return retval;
}
public void remove() {
throw new UnsupportedOperationException(
"Removal not supported on element iterator");
}
/**
* Returns the next sequential element in the iteration order or
* {@code null} if there are no remaining elements.
*
* @return next element or {@code null}
*/
private Element findNextElement() {
// If we are iterating a sublist and elements remain, return the element
// in the sublist.
if (sublistIterator != null) {
if (sublistIterator.hasNext()) {
return sublistIterator.next();
}
// Done with the sublist iterator.
sublistIterator = null;
}
Element next = null;
while (next == null && mode != Mode.DONE) {
switch(mode) {
case DECLARED:
next = findNextDeclaredElement();
break;
case UNDECLARED:
next = findNextUndeclaredElement();
break;
default:
break;
}
}
return next;
}
/**
* Finds the next declared element, if one exists, otherwise returns null.
* Will set the mode to undeclared if nothing was found.
*/
private Element findNextDeclaredElement() {
if (metadataIterator != null) {
while (metadataIterator.hasNext()) {
ElementKey<?, ?> nextKey = metadataIterator.next();
if (ElementCreatorImpl.ELEMENT_MARKER == nextKey) {
mode = Mode.UNDECLARED;
return null;
}
ElementMetadata<?, ?> childMeta = metadata.bindElement(nextKey);
if (!childMeta.isVisible()) {
continue;
}
SingleVirtualElement singleVirtual =
childMeta.getSingleVirtualElement();
if (singleVirtual != null) {
Element generated =
singleVirtual.generateSingle(element, metadata, childMeta);
if (generated != null) {
return generated;
}
}
MultipleVirtualElement multipleVirtual =
childMeta.getMultipleVirtualElement();
if (multipleVirtual != null) {
Collection<? extends Element> virtualElements =
multipleVirtual.generateMultiple(element, metadata, childMeta);
if (virtualElements != null && !virtualElements.isEmpty()) {
sublistIterator = virtualElements.iterator();
return sublistIterator.next();
}
}
Object obj = getElementObject(nextKey.getId());
Element first = firstElement(obj);
if (first != null) {
return first;
}
}
// No more declared elements, turn the iterator off.
metadataIterator = null;
}
// Check undeclared next.
mode = Mode.UNDECLARED;
return null;
}
/**
* Find the next undeclared element, or null if no more undeclared elements
* exist.
*/
private Element findNextUndeclaredElement() {
if (elementIterator != null) {
while (elementIterator.hasNext()) {
Object next = elementIterator.next();
Element first = firstElement(next);
if (first != null && isUndeclared(first.getElementKey())) {
return first;
} else {
// Clear the sublist iterator if the first element wasn't valid.
sublistIterator = null;
}
}
// No more undeclared elements, turn the iterators off.
sublistIterator = null;
elementIterator = null;
}
// Go back and check for any remaining declared metadata.
mode = metadataIterator != null && metadataIterator.hasNext()
? Mode.DECLARED : Mode.DONE;
return null;
}
/**
* Get the first element from either a singleton or a collection of
* elements. This method will also set the sublistIterator as a side effect
* of retrieving the first element in a collection.
*/
private Element firstElement(Object obj) {
if (obj == null) {
return null;
}
// If the next declared element is a single instance, then just
// return it. On the next call we will move to the next declared
// type.
if (obj instanceof Element) {
return (Element) obj;
} else {
// If the next declared element is a collection of elements, set the
// sublist iterator to the content of the collection and return the
// first element. The next call will continue iteration on this
// collection.
Collection<Element> elementCollection = castElementCollection(obj);
if (!elementCollection.isEmpty()) {
sublistIterator = elementCollection.iterator();
return sublistIterator.next();
}
}
return null;
}
/**
* Suppress the warnings around casting the object in the map into a
* collection of elements.
*/
@SuppressWarnings("unchecked")
private <T extends Element> Collection<T> castElementCollection(Object obj) {
return (Collection<T>) obj;
}
/**
* This method just returns the bare object stored in the map, or null if
* either the map didn't contain the object or the map is null.
*/
private Object getElementObject(QName id) {
return (elements != null) ? elements.get(id) : null;
}
/**
* Returns true if the given element was not declared.
*/
private boolean isUndeclared(ElementKey<?, ?> key) {
return (metadata == null) || !metadata.isDeclared(key);
}
}