/* 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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This class provides an ElementVisitor implementation that aggregates multiple
* nested ElementVisitor instances. This makes it possible to apply several
* different unrelated visitor implementations during a single data model tree
* traversal.
*/
public class CompositeElementVisitor implements ElementVisitor {
/**
* The visitors field contains a list of the active ElementVisitor instances
* for the composite visitor.
*/
private final List<ElementVisitor> visitors;
/**
* Contains the collection of inactive ElementVisitor instance and the
* {@link ElementVisitor.StoppedException} that made them inactive.
*/
private final Map<ElementVisitor, ElementVisitor.StoppedException>
stoppedVisitors =
new HashMap<ElementVisitor, ElementVisitor.StoppedException>();
/**
* The ignoringVisitors map contains information about visitors that are
* currently ignoring child events. The key will be the visitor instance that
* is ignoring child events and the value will be the element beneath which
* all child events are being ignored.
*/
private final Map<ElementVisitor, Element> ignoringVisitors =
new HashMap<ElementVisitor, Element>();
/**
* Constructs a new CompositeElementVisitor instance containing the array of
* visitor instances passed in. The order in which each visitor instance
* appears in the array is significant; it defines the order in which events
* for a given element will be delivered to the nested instances.
*
* @param visitors list of ElementVisitor instances to compose.
*/
public CompositeElementVisitor(ElementVisitor... visitors) {
this.visitors = new ArrayList<ElementVisitor>(visitors.length);
for (ElementVisitor visitor : visitors) {
this.visitors.add(visitor);
}
}
/**
* Adds a new {@link ElementVisitor} instance to the composite visitor. For
* any element, events will be delivered to the added visitor after events
* have been delivered to other visitors already contained in the composite
* visitor at the time of addition.
*
* @param visitor the new visitor to add.
*/
public void addVisitor(ElementVisitor visitor) {
visitors.add(visitor);
}
/**
* Returns the list of active element visitor instances. Any stopped visitors
* will not appear in this list.
*
* @return list of visitor instances in order of event delivery.
*/
public List<ElementVisitor> getVisitors() {
return visitors;
}
/**
* Returns any StoppedException thrown by the specified visitor, or
* {@code null} if the visitor completed normally.
*/
public StoppedException getStoppedException(ElementVisitor visitor) {
return stoppedVisitors.get(visitor);
}
public boolean visit(Element parent, Element target,
ElementMetadata<?, ?> metadata) throws StoppedException {
// True if any nested visitor has stopped
boolean newStopped = false;
// Iterate through the list of active visitors
for (ElementVisitor visitor : visitors) {
// If ignoring child events, skip all events until a visitComplete
// event is delivered on the associated extension point.
if (ignoringVisitors.containsKey(visitor)) {
continue;
}
try {
boolean visitChildren = visitor.visit(parent, target, metadata);
if (!visitChildren) {
ignoringVisitors.put(visitor, target);
}
} catch (ElementVisitor.StoppedException se) {
stoppedVisitors.put(visitor, se);
newStopped = true;
}
}
// Remove any stopped visitors and associated state.
if (newStopped) {
visitors.removeAll(stoppedVisitors.keySet());
if (visitors.isEmpty()) {
throw new ElementVisitor.StoppedException("All visitors stopped");
}
}
// If all visitors are ignoring children, then don't traverse into them
return visitors.size() != ignoringVisitors.size();
}
public void visitComplete(Element parent, Element target,
ElementMetadata<?, ?> metadata) throws StoppedException {
// Check any visitors that are currently ignoring children to see if
// they should be re-enabled as a result of this event.
List<ElementVisitor> resetList = null;
for (Map.Entry<ElementVisitor, Element> stateEntry : ignoringVisitors
.entrySet()) {
Element ignoring = stateEntry.getValue();
if (ignoring == target) {
if (resetList == null) {
resetList = new ArrayList<ElementVisitor>();
}
resetList.add(stateEntry.getKey());
}
}
// Removed re-enabled visitors
if (resetList != null) {
for (ElementVisitor enabledVisitor : resetList) {
ignoringVisitors.remove(enabledVisitor);
}
}
// Deliver the visitComplete event to all active visitors.
for (ElementVisitor visitor : visitors) {
if (!ignoringVisitors.containsKey(visitor)) {
visitor.visitComplete(parent, target, metadata);
}
}
}
}