| /******************************************************************************* |
| * Copyright (c) 2001, 2008 Oracle Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Oracle Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jst.jsf.common.runtime.internal.model.component; |
| |
| import java.beans.BeanInfo; |
| import java.beans.IntrospectionException; |
| import java.beans.Introspector; |
| import java.beans.PropertyDescriptor; |
| import java.io.Serializable; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Map.Entry; |
| |
| import org.eclipse.jst.jsf.common.runtime.internal.model.ViewObject; |
| import org.eclipse.jst.jsf.common.runtime.internal.model.component.AbstractVisitor.VisitationPolicy; |
| import org.eclipse.jst.jsf.common.runtime.internal.model.decorator.FacetDecorator; |
| |
| /** |
| * Models a basic UI component instance |
| * |
| * TODO: should implement a visitor pattern to traverse component trees |
| * |
| * @author cbateman |
| * |
| */ |
| public class ComponentInfo extends ViewObject implements Serializable, |
| IVisitable |
| { |
| /** |
| * serializable id |
| */ |
| private static final long serialVersionUID = 2517204356825585699L; |
| |
| private final static int DEFAULT_ARRAY_SIZE = 4; |
| |
| private transient BeanPropertyManager _beanPropertyManager; |
| /** |
| * Encapsulates all of the data for the view object |
| */ |
| protected final ComponentInfoData _data; |
| |
| // initialized |
| // by |
| // getBeanProperties |
| |
| /** |
| * @param id |
| * @param parent |
| * @param componentTypeInfo |
| * @param isRendered |
| */ |
| protected ComponentInfo(final String id, final ComponentInfo parent, |
| final ComponentTypeInfo componentTypeInfo, final boolean isRendered) |
| { |
| super(new ComponentInfoData(id, parent, componentTypeInfo, isRendered)); |
| _data = (ComponentInfoData) super.getData(); |
| |
| final Set propExclude = new HashSet(); |
| propExclude.add("attributeNames"); //$NON-NLS-1$ |
| propExclude.add("componentTypeInfo"); //$NON-NLS-1$ |
| propExclude.add("valueChangeListeners"); //$NON-NLS-1$ |
| propExclude.add("visitableChildren"); //$NON-NLS-1$ |
| |
| _beanPropertyManager = new BeanPropertyManager(this, propExclude); |
| } |
| |
| /** |
| * @param data |
| */ |
| protected ComponentInfo(final ComponentInfoData data) |
| { |
| super(data); |
| _data = data; |
| } |
| |
| /** |
| * Construct a new component info using the attributes keyed by name in |
| * attributes to set values. The names must match the corresponding bean |
| * property names. Primitives should be wrapped in their corresponding |
| * object types. Exceptions will be thrown if there is a type mismatch on an |
| * expected type. Number will be used for all numeric primitive wrappers an |
| * the corresponding "to" will be called. |
| * |
| * @param parent |
| * @param componentTypeInfo |
| * @param attributes |
| * @throws ClassCastException |
| * if an attribute's value doesn't match the expected type |
| * @throws NullPointerException |
| * if an attribute value is null for a value whose type is |
| * expected to be primitive |
| * @throws IllegalArgumentException |
| * if attributes does not contain a required key. |
| */ |
| protected ComponentInfo(final ComponentInfo parent, |
| final ComponentTypeInfo componentTypeInfo, final Map attributes) |
| { |
| this(getStringProperty("id", attributes, false), parent, //$NON-NLS-1$ |
| componentTypeInfo, getBooleanProperty("rendered", attributes, false)); //$NON-NLS-1$ |
| } |
| |
| /** |
| * @param key |
| * @param attributes |
| * @param mandatory |
| * @return the value in attributes at location key, forcing a |
| * ClassCastException if it turns out not to be a String. |
| * @throws ClassCastException |
| * if the attribute for key is not a String |
| * @throws IllegalArgumentException |
| * if the attribute for key is null but mandatory is true. |
| */ |
| protected static String getStringProperty(final String key, |
| final Map attributes, final boolean mandatory) |
| { |
| final Object value = attributes.get(key); |
| |
| if (mandatory && value == null) |
| { |
| throw new IllegalArgumentException(key |
| + " is a mandatory attribute"); //$NON-NLS-1$ |
| } |
| return (String) value; |
| } |
| |
| /** |
| * @param key |
| * @param attributes |
| * @param mandatory |
| * |
| * @return the value in attributes at location, forcing a ClassCastExceptio |
| * if it is not a Boolean and mandatory. returns false if no value |
| * and not mandatory |
| * @throws IllegalArgumentException |
| * if key is not found and value is mandatory |
| */ |
| protected static boolean getBooleanProperty(final String key, |
| final Map attributes, final boolean mandatory) |
| { |
| final Boolean value = (Boolean) attributes.get(key); |
| |
| if (value == null) |
| { |
| if (mandatory) |
| { |
| throw new IllegalArgumentException(key + "is mandatory"); //$NON-NLS-1$ |
| } |
| return false; |
| } |
| |
| return value.booleanValue(); |
| } |
| |
| /** |
| * @param key |
| * @param attributes |
| * @return the integer property for key. Casts the value to Number and calls |
| * Number.intValue(). 0 if no value. |
| */ |
| protected static int getIntegerProperty(final String key, |
| final Map attributes) |
| { |
| final Number value = (Number) attributes.get(key); |
| |
| if (value == null) |
| { |
| return 0; |
| } |
| |
| return value.intValue(); |
| } |
| |
| /** |
| * @param key |
| * @param attributes |
| * @return the component info value from attributes |
| */ |
| protected static ComponentInfo getComponentProperty(final String key, |
| final Map attributes) |
| { |
| return (ComponentInfo) attributes.get(key); |
| } |
| |
| /** |
| * @return the id |
| */ |
| public final String getId() |
| { |
| return _data.getId(); |
| } |
| |
| /** |
| * @return the component type info |
| */ |
| public final ComponentTypeInfo getComponentTypeInfo() |
| { |
| return _data.getComponentTypeInfo(); |
| } |
| |
| /** |
| * Pre-condition: isModifiable() == true Post-condition: getChildren() will |
| * return an empty list. |
| */ |
| protected final void clearChildren() |
| { |
| _data.getChildren().clear(); |
| } |
| |
| /** |
| * @return the children. List is unmodifiable. List contains all children |
| * including facets. |
| */ |
| public final List/* <ComponentInfo> */getChildren() |
| { |
| if (_data.isProtected()) |
| { |
| return _data.getChildren(); |
| } |
| return Collections.unmodifiableList(_data.getChildren()); |
| } |
| |
| /** |
| * Get the sub-set of {@link #getChildren()} that are facets. This is a |
| * convenience method for {@link #getDecorators(Class)} |
| * |
| * @return all component children that are facets |
| */ |
| public final List getFacets() |
| { |
| return getDecorators(ComponentFactory.FACET); |
| } |
| |
| /** |
| * @param childComponent |
| */ |
| public final void addChild(final ComponentInfo childComponent) |
| { |
| if (childComponent == this) |
| { |
| throw new IllegalArgumentException( |
| "A component cannot be its own child"); //$NON-NLS-1$ |
| } |
| _data.addChild(childComponent); |
| // we need to reset the child's parent to me |
| childComponent.setParent(this); |
| } |
| |
| /** |
| * @param parent |
| */ |
| public final void setParent(ComponentInfo parent) |
| { |
| _data.setParent(parent); |
| } |
| |
| /** |
| * @param name |
| * @param facetComponent |
| */ |
| public final void addFacet(final String name, |
| final ComponentInfo facetComponent) |
| { |
| addChild(facetComponent); |
| addDecorator(new FacetDecorator(name, facetComponent)); |
| } |
| |
| /** |
| * @param component |
| * @return if component corresponds to a facet of this component, returns |
| * the name of that facet. Returns null if not found. |
| */ |
| public final String getFacetName(final ComponentInfo component) |
| { |
| if (component == null) |
| { |
| return null; |
| } |
| |
| final List facets = getDecorators(ComponentFactory.FACET); |
| |
| for (final Iterator it = facets.iterator(); it.hasNext();) |
| { |
| final FacetDecorator facet = (FacetDecorator) it.next(); |
| if (component == facet.getDecorates()) |
| { |
| return facet.getName(); |
| } |
| } |
| |
| // component is not a facet |
| return null; |
| } |
| |
| /** |
| * @param name |
| * @return if this has a facet called name, then returns it's single root |
| * component. |
| */ |
| public final ComponentInfo getFacet(final String name) |
| { |
| if (name == null) |
| { |
| return null; |
| } |
| |
| final List facets = getDecorators(ComponentFactory.FACET); |
| |
| for (final Iterator it = facets.iterator(); it.hasNext();) |
| { |
| final FacetDecorator facet = (FacetDecorator) it.next(); |
| if (name.equals(facet.getName())) |
| { |
| return facet.getDecorates(); |
| } |
| } |
| |
| // not found |
| return null; |
| } |
| |
| public String toString() |
| { |
| final String parentId = getParent() != null ? getParent().getId() |
| : "null"; //$NON-NLS-1$ |
| String toString = getMostSpecificComponentName() + ": id=" //$NON-NLS-1$ |
| + _data.getId() + ", parentId: " + parentId + ", family=" //$NON-NLS-1$ //$NON-NLS-2$ |
| + getComponentTypeInfo().getComponentFamily() + ", render=" //$NON-NLS-1$ |
| + getComponentTypeInfo().getRenderFamily() + ", rendered=" //$NON-NLS-1$ |
| + isRendered(); |
| |
| // use bean introspection to dump child properties |
| if (this.getClass() != ComponentInfo.class) |
| { |
| toString += dumpProperties(); |
| } |
| |
| return toString; |
| } |
| |
| private String dumpProperties() |
| { |
| String properties = ""; //$NON-NLS-1$ |
| try |
| { |
| final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass(), |
| ComponentInfo.class); |
| |
| final PropertyDescriptor[] descriptors = beanInfo |
| .getPropertyDescriptors(); |
| for (int i = 0; i < descriptors.length; i++) |
| { |
| final PropertyDescriptor desc = descriptors[i]; |
| final String name = desc.getName(); |
| final Object valueObj = desc.getValue(name); |
| final String value = valueObj != null ? valueObj.toString() |
| : "null"; //$NON-NLS-1$ |
| properties += ", " + name + "=" + value; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| catch (final IntrospectionException e) |
| { |
| return "Error introspecting bean: " + e.getLocalizedMessage(); //$NON-NLS-1$ |
| } |
| |
| return properties; |
| } |
| |
| /** |
| * @return used for toString. Clients should not use. |
| */ |
| protected String getMostSpecificComponentName() |
| { |
| return "UIComponent"; //$NON-NLS-1$ |
| } |
| |
| /** |
| * @return the parent of this component or null. |
| */ |
| public final ComponentInfo getParent() |
| { |
| return _data.getParent(); |
| } |
| |
| /** |
| * @return the rendered flag |
| */ |
| public final boolean isRendered() |
| { |
| return _data.isRendered(); |
| } |
| |
| public synchronized void addAdapter(final Class adapterType, |
| final Object adapter) |
| { |
| super.addAdapter(adapterType, adapter); |
| |
| // force an update on the next call to getBeanProperties |
| _beanPropertyManager.reset(); |
| } |
| |
| public synchronized Object removeAdapter(final Class adapterType) |
| { |
| final Object removed = super.removeAdapter(adapterType); |
| |
| _beanPropertyManager.reset(); |
| |
| return removed; |
| } |
| |
| /** |
| * @return the set of all bean property names for this component. The set is |
| * unmodifiable and will throw exceptions if modification is |
| * attempted. |
| */ |
| protected final Map/* <String, ComponentBeanProperty> */getBeanProperties() |
| { |
| return Collections.unmodifiableMap(_beanPropertyManager |
| .getBeanProperties()); |
| } |
| |
| /** |
| * @author cbateman |
| * |
| */ |
| public static class ComponentInfoData extends ViewObjectData |
| { |
| /** |
| * |
| */ |
| private static final long serialVersionUID = 5052732412917986062L; |
| /** |
| * the component id |
| */ |
| private final String _id; |
| /** |
| * the component's parent or null if none |
| */ |
| private ComponentInfo _parent; |
| |
| /** |
| * the type info for this component |
| */ |
| protected final ComponentTypeInfo _componentTypeInfo; |
| /** |
| * the rendered flage |
| */ |
| protected final boolean _isRendered; |
| |
| private List /* <ComponentInfo> */_children = new ArrayList( |
| DEFAULT_ARRAY_SIZE); |
| |
| /** |
| * @param id |
| * @param parent |
| * @param componentTypeInfo |
| * @param isRendered |
| */ |
| public ComponentInfoData(final String id, ComponentInfo parent, |
| ComponentTypeInfo componentTypeInfo, boolean isRendered) |
| { |
| super(false); |
| _id = id; |
| _parent = parent; |
| _componentTypeInfo = componentTypeInfo; |
| _isRendered = isRendered; |
| } |
| |
| /** |
| * @param childComponent |
| */ |
| protected void addChild(ComponentInfo childComponent) |
| { |
| enforceProtection(); |
| |
| getChildren().add(childComponent); |
| } |
| |
| /** |
| * @return the modifiable list of children |
| */ |
| protected final List/* <ComponentInfo> */getChildren() |
| { |
| return _children; |
| } |
| |
| protected void doBeforeProtecting() |
| { |
| super.doBeforeProtecting(); |
| // compact the children array list |
| if (_children.size() > 0) |
| { |
| _children = Collections.unmodifiableList(_children); |
| } |
| else |
| { |
| _children = Collections.EMPTY_LIST; |
| } |
| } |
| |
| /** |
| * @return the isRendered flag |
| */ |
| protected final boolean isRendered() |
| { |
| return _isRendered; |
| } |
| |
| /** |
| * @return the component type info flag |
| */ |
| protected final ComponentTypeInfo getComponentTypeInfo() |
| { |
| return _componentTypeInfo; |
| } |
| |
| /** |
| * @return the parent or null if no parent |
| */ |
| protected final ComponentInfo getParent() |
| { |
| return _parent; |
| } |
| |
| /** |
| * @param parent |
| */ |
| protected final void setParent(ComponentInfo parent) |
| { |
| enforceProtection(); |
| _parent = parent; |
| } |
| |
| /** |
| * @return the component id |
| */ |
| protected final String getId() |
| { |
| return _id; |
| } |
| } |
| |
| /** |
| * This is similar to the runtime getAttributes().get(name) call. The reason |
| * we don't implement a Map of all attribute values is that the implicit |
| * property structure can change at any time due to add/removeAdapter. To |
| * get all attributes known for a component, instead use: |
| * |
| * The synchronized block is advised to protect against concurrent |
| * modification exceptions on the keySet iterator. |
| * |
| * @param name |
| * |
| * @return the value of the attribute or null if none. |
| * |
| */ |
| public synchronized ComponentBeanProperty getAttribute(final String name) |
| { |
| return (ComponentBeanProperty) getBeanProperties().get(name); |
| } |
| |
| /** |
| * @return the set of valid attribute names. The Set is not modifiable. |
| */ |
| public synchronized Set/* <String> */getAttributeNames() |
| { |
| return getBeanProperties().keySet(); |
| } |
| |
| /** |
| * Stores a bean property descriptor along information about which |
| * implementation class declares it and what key to pass to getAdapter() in |
| * order to get it. |
| * |
| */ |
| public final static class ComponentBeanProperty |
| { |
| private final PropertyDescriptor _propertyDescriptor; |
| private final Object _declaringImplementation; |
| private final Class _adapterKeyClass; |
| |
| // only instantiable locally |
| private ComponentBeanProperty(Class adapterKeyClass, |
| Object declaringImplementationClass, |
| PropertyDescriptor propertyDescriptor) |
| { |
| super(); |
| _adapterKeyClass = adapterKeyClass; |
| _declaringImplementation = declaringImplementationClass; |
| _propertyDescriptor = propertyDescriptor; |
| } |
| |
| /** |
| * @return the value of property |
| */ |
| public final Object getValue() |
| { |
| final Method method = _propertyDescriptor.getReadMethod(); |
| if (method != null) |
| { |
| try |
| { |
| method.setAccessible(true); |
| return method.invoke(_declaringImplementation, |
| new Object[0]); |
| } |
| catch (IllegalArgumentException e) |
| { |
| e.printStackTrace(); |
| } |
| catch (IllegalAccessException e) |
| { |
| e.printStackTrace(); |
| } |
| catch (InvocationTargetException e) |
| { |
| e.printStackTrace(); |
| } |
| } |
| // if any step fails, return null |
| return null; |
| } |
| |
| /** |
| * @return the property descriptor |
| */ |
| public final PropertyDescriptor getPropertyDescriptor() |
| { |
| return _propertyDescriptor; |
| } |
| |
| /** |
| * @return the implemenation |
| */ |
| public final Object getDeclaringImplementationClass() |
| { |
| return _declaringImplementation; |
| } |
| |
| /** |
| * @return the adapter class for the interface that the declaring |
| * implementation is providing the impl for |
| */ |
| public final Class getAdapterKeyClass() |
| { |
| return _adapterKeyClass; |
| } |
| } |
| |
| /** |
| * Manages bean property information for a component |
| * |
| * @author cbateman |
| * |
| */ |
| protected final static class BeanPropertyManager |
| { |
| /** |
| * a map of the bean property names exposed by this component including |
| * all those added by addAdapter(). |
| * |
| * this is synthetic based the class definition and installed adapters |
| * so as long that info is available, no need to serialize. |
| */ |
| protected transient Map /* |
| * <String, |
| * ComponentBeanProperty> |
| */_beanProperties; // lazily |
| private final transient ComponentInfo _component; |
| private final transient Set _excludeNames; |
| |
| /** |
| * @param component |
| * @param excludeNames |
| */ |
| protected BeanPropertyManager(final ComponentInfo component, |
| final Set excludeNames) |
| { |
| _component = component; |
| _excludeNames = excludeNames; |
| } |
| |
| /** |
| * Will throw exception of the calling thread already holds the "this" |
| * monitor lock. This is to ensure that caller always acquires locks in |
| * appropriate order to prevent deadlock. |
| * |
| * @return the internal set of bean properties. This Set may be modified |
| * internally. |
| */ |
| public Map getBeanProperties() |
| { |
| if (Thread.holdsLock(this)) |
| { |
| throw new IllegalStateException( |
| "Must not already own this lock"); //$NON-NLS-1$ |
| } |
| |
| // must always acquire component lock first to prevent deadlock |
| synchronized (_component) |
| { |
| synchronized (this) |
| { |
| if (_beanProperties == null) |
| { |
| _beanProperties = calculateAllBeanPropNames(ViewObject.class); |
| } |
| |
| return _beanProperties; |
| } |
| } |
| } |
| |
| /** |
| * Will throw exception if the calling thread already holds the "this" |
| * monitor lock. This is to ensure that caller always acquires locks in |
| * appropriate order to prevent deadlock. |
| * |
| * Clears the internal map and sets to null. This will force it to be |
| * completely new built on the next call to getBeanProperties |
| */ |
| public void reset() |
| { |
| if (Thread.holdsLock(this)) |
| { |
| throw new IllegalStateException( |
| "Must not already own this lock"); //$NON-NLS-1$ |
| } |
| |
| // must always acquire component lock first to prevent deadlock |
| synchronized (_component) |
| { |
| synchronized (this) |
| { |
| if (_beanProperties != null) |
| { |
| _beanProperties.clear(); |
| _beanProperties = null; |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param stopClass |
| * @return a synchronized map of all bean property names on this class |
| * up to stopClass, as well as all adapter property names (as |
| * though this really implemented them). |
| */ |
| private Map calculateAllBeanPropNames(final Class stopClass) |
| { |
| // use a set to prevents the duplicates |
| final Map allProperties = new HashMap(); |
| |
| { |
| final Class myClass = _component.getClass(); |
| final List myProperties = getOrCreateBeanProperties(myClass, |
| stopClass); |
| |
| addToMap(myProperties, _component, myClass, allProperties, _excludeNames); |
| } |
| |
| { |
| for (final Iterator it = _component.getAdapterMap().entrySet() |
| .iterator(); it.hasNext();) |
| { |
| Map.Entry entry = (Entry) it.next(); |
| |
| final Class adapterClass = (Class) entry.getKey(); |
| final Object declaringClass = entry.getValue(); |
| // get all props, excluding the ones on Object. |
| final List props = getOrCreateBeanProperties(adapterClass, |
| null); |
| addToMap(props, declaringClass, adapterClass, allProperties, |
| _excludeNames); |
| } |
| } |
| |
| return Collections.synchronizedMap(allProperties); |
| } |
| |
| private static void addToMap( |
| final List/* <ComponentBeanProperty> */addThese, |
| final Object declaringObject, final Class declaringAdapter, |
| final Map toMe, |
| Set excludeNames) |
| { |
| for (final Iterator it = addThese.iterator(); it.hasNext();) |
| { |
| final PropertyDescriptor desc = (PropertyDescriptor) it.next(); |
| |
| if (!toMe.containsKey(desc.getName()) |
| && !excludeNames.contains(desc.getName())) |
| { |
| toMe.put(desc.getName(), new ComponentBeanProperty( |
| declaringAdapter, declaringObject, desc)); |
| } |
| } |
| } |
| |
| /** |
| * lazily loaded with the local properties (those not defined using |
| * adapters) |
| * |
| * MUST INITIALIZE early so can synchronize on it |
| */ |
| private transient static Map /* <Class, List<PropertyDescriptor> */PROPERTY_MAP = new HashMap(); |
| |
| /** |
| * @param startClass |
| * @param stopClass |
| * @return a unmodifiable list of properties starting from startClass. |
| * stopClass is only used if an entry doesn't already exist in |
| * PROPERTY_MAP for startClass. The method is synchronized on |
| * the PROPERTY_MAP it updates. |
| */ |
| protected static List/* <PropertyDescriptor */getOrCreateBeanProperties( |
| final Class startClass, final Class stopClass) |
| { |
| synchronized (PROPERTY_MAP) |
| { |
| List localBeanProps = (List) PROPERTY_MAP.get(startClass); |
| |
| if (localBeanProps == null) |
| { |
| localBeanProps = calculateBeanProperties(startClass, |
| stopClass); |
| PROPERTY_MAP.put(startClass, Collections |
| .unmodifiableList(localBeanProps)); |
| } |
| |
| return localBeanProps; |
| } |
| } |
| |
| /** |
| * @param startClass |
| * @param stopClass |
| * @return a List<String> containing all of the bean names between |
| * startClass and stopClass. Start class must be a descendant |
| * (sub-class, sub-sub-class etc.) of stopClass. The properties |
| * on stopClass are excluded from analysis. |
| */ |
| private static List/* <PropertyDescriptor> */calculateBeanProperties( |
| final Class startClass, final Class stopClass) |
| { |
| BeanInfo beanInfo; |
| List names = new ArrayList(); |
| |
| try |
| { |
| beanInfo = Introspector.getBeanInfo(startClass, stopClass); |
| final PropertyDescriptor[] descriptors = beanInfo |
| .getPropertyDescriptors(); |
| |
| if (descriptors != null) |
| { |
| names = Arrays.asList(descriptors); |
| } |
| } |
| catch (final IntrospectionException e) |
| { |
| e.printStackTrace(); |
| } |
| return names; |
| } |
| |
| } |
| |
| /** |
| * Visits this node and it's entire tree and makes all nodes protected. |
| */ |
| public final void setSubtreeProtected() |
| { |
| // lock children first |
| final ComponentTreeVisitor protectionVisitor = new ComponentTreeVisitor(VisitationPolicy.ChildrenFirstPolicy) |
| { |
| public void visit(ComponentInfo component) |
| { |
| component.setProtected(); |
| } |
| }; |
| |
| accept(protectionVisitor); |
| } |
| |
| public void accept(AbstractVisitor visitor) |
| { |
| // check policy ordering |
| if (visitor.getPolicy().getOrdering() == VisitationPolicy.VISIT_PARENT_FIRST) |
| { |
| visitor.visit(this); |
| visitChildren(visitor); |
| } |
| else |
| { |
| visitChildren(visitor); |
| visitor.visit(this); |
| } |
| } |
| |
| private void visitChildren(AbstractVisitor visitor) |
| { |
| for (final Iterator it = getVisitableChildren(); it.hasNext();) |
| { |
| visitor.visit(it.next()); |
| } |
| } |
| |
| public Iterator getVisitableChildren() |
| { |
| return getChildren().iterator(); |
| } |
| } |