zk/src/main/java/org/zkoss/zk/ui/HtmlShadowElement.java

Summary

Maintainability
F
2 wks
Test Coverage
/** HtmlShadowElement.java.

    Purpose:
        
    Description:
        
    History:
        12:47:42 PM Oct 22, 2014, Created by jumperchen

Copyright (C) 2014 Potix Corporation. All Rights Reserved.
 */
package org.zkoss.zk.ui;

import java.util.AbstractSequentialList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SerializableEventListener;
import org.zkoss.zk.ui.metainfo.Annotation;
import org.zkoss.zk.ui.sys.ComponentCtrl;
import org.zkoss.zk.ui.sys.ShadowElementsCtrl;
import org.zkoss.zk.ui.util.Callback;

/**
 * A skeleton of shadow element that represents as a <i>shadow</i> tree.
 * 
 * @author jumperchen
 * @since 8.0.0
 */
public abstract class HtmlShadowElement extends AbstractComponent implements ShadowElement, ShadowElementCtrl {
    private static final Logger log = LoggerFactory.getLogger(HtmlShadowElement.class);
    private static final long serialVersionUID = 20141022145906L;
    private Component _firstInsertion;
    private Component _lastInsertion;
    private Component _nextInsertion;
    private Component _previousInsertion;
    protected boolean _afterComposed = false;

    private Component _host;

    protected static String ON_REBUILD_SHADOW_TREE_LATER = "onRebuildShadowTreeLater";

    //internal used only
    public static String SKIP_DISTRIBUTED_CHILDREN_PROPERTY_CHANGE = "skipDistributedChildrenPropertyChange";

    public Object resolveVariable(Component child, String name, boolean recurse) {
        if (_firstInsertion == null) // out of our range;
            return null;

        if (child == null || child.getParent() == null) {
            return getAttributeOrFellow(name, recurse);
        }

        List<Component> children = child.getParent().getChildren();
        int insertIndex = children.indexOf(child);
        int selfFirstIndex = children.indexOf(_firstInsertion);
        if (insertIndex < selfFirstIndex)
            return null; // out of our range;

        Map<Component, Integer> indexMap = fillUpIndexMap(_firstInsertion, _lastInsertion);
        int[] selfIndex = getInsertionIndex(_firstInsertion, _lastInsertion, indexMap);
        if (selfIndex[1] < insertIndex)
            return null; // out of our range;

        HtmlShadowElement node = queryIntersectedShadowIfAny(insertIndex, indexMap);
        if (node == this)
            return node.getShadowVariable(name, recurse);
        return null;
    }

    /**
     * Returns the next component before this shadow, if any. (it will invoke recursively from its parent.)
     */
    public Component getNextInsertionComponentIfAny() {
        if (_nextInsertion == null) {
            Component result = _lastInsertion == null ? null : _lastInsertion.getNextSibling();
            if (result == null && getParent() != null) { // ask for its parent
                return asShadow(getParent()).getNextInsertionComponentIfAny();
            }
            return result;
        } else if (_nextInsertion instanceof HtmlShadowElement) {
            HtmlShadowElement nextInsertion = asShadow(_nextInsertion);
            // ask for the firstInsertion first.
            if (nextInsertion._firstInsertion != null)
                return nextInsertion._firstInsertion;
            return nextInsertion.getNextInsertionComponentIfAny();
        }
        return _nextInsertion;
    }

    /**
     * Returns the first component before this shadow, if any. (it will invoke recursively from its parent.)
     */
    public Component getPreviousInsertionComponentIfAny() {
        if (_previousInsertion == null) {
            Component result = _firstInsertion == null ? null : _firstInsertion.getNextSibling();
            if (result == null && getParent() != null) // ask for its parent
                return asShadow(getParent()).getPreviousInsertionComponentIfAny();
            return result;
        } else if (_previousInsertion instanceof HtmlShadowElement) {
            HtmlShadowElement previousInsertion = asShadow(_previousInsertion);
            // ask for the lastInsertion first.
            if (previousInsertion._lastInsertion != null)
                return previousInsertion._lastInsertion;
            return previousInsertion.getPreviousInsertionComponentIfAny();
        }
        return _previousInsertion;
    }

    protected void onHostAttached(Component host) {
        Iterable<EventListener<? extends Event>> eventListeners = host.getEventListeners(ON_REBUILD_SHADOW_TREE_LATER);
        if (!eventListeners.iterator().hasNext()) {
            host.addEventListener(ON_REBUILD_SHADOW_TREE_LATER, new SerializableEventListener<Event>() {

                public void onEvent(Event event) throws Exception {
                    Component target = event.getTarget();
                    if (target instanceof ComponentCtrl && target.getDesktop() != null) {
                        for (ShadowElement se : new ArrayList<ShadowElement>(
                                ((ComponentCtrl) target).getShadowRoots())) {
                            if (se instanceof HtmlShadowElement) {
                                ((HtmlShadowElement) se).rebuildShadowTree();
                            }
                        }
                    } else { // cleanup
                        Iterable<EventListener<? extends Event>> eventListeners = target
                                .getEventListeners(ON_REBUILD_SHADOW_TREE_LATER);
                        for (EventListener<? extends Event> listener : eventListeners) {
                            target.removeEventListener(ON_REBUILD_SHADOW_TREE_LATER, listener);
                        }
                    }
                }

            });
        }
    }

    protected void onHostDetached(Component host) {
        if (host instanceof ComponentCtrl) {
            if (((ComponentCtrl) host).getShadowRoots().isEmpty()) {
                Iterable<EventListener<? extends Event>> eventListeners = host
                        .getEventListeners(ON_REBUILD_SHADOW_TREE_LATER);
                for (EventListener<? extends Event> listener : eventListeners) {
                    host.removeEventListener(ON_REBUILD_SHADOW_TREE_LATER, listener);
                }
            }
        }
    }

    /**
     * Returns the next insertion point, it may be a component, a shadow element, or null.
     */
    public Component getNextInsertion() {
        return _nextInsertion;
    }

    /**
     * Returns the previous insertion point, it may be a component, a shadow element, or null.
     */
    public Component getPreviousInsertion() {
        return _previousInsertion;
    }

    /**
     * Updates the given previous insertion to this shadow element. (Internal use only)
     * @since 10.0.0
     */
    public void updatePreviousInsertion(Component newPreviousInsertion) {
        if (_previousInsertion == newPreviousInsertion)
            return; // do nothing

        _previousInsertion = newPreviousInsertion;

        if (newPreviousInsertion instanceof HtmlShadowElement) {
            asShadow(newPreviousInsertion)._nextInsertion = this;
        }
    }

    /**
     * Updates the given first insertion to this shadow element. (Internal use only)
     * @since 10.0.0
     */
    public void updateFirstInsertion(Component newFirstInsertion) {
        _firstInsertion = newFirstInsertion;
    }

    /**
     * Updates the given next insertion to this shadow element. (Internal use only)
     * @since 10.0.0
     */
    public void updateNextInsertion(Component newNextInsertion) {
        if (_nextInsertion == newNextInsertion)
            return; // do nothing

        _nextInsertion = newNextInsertion;

        if (newNextInsertion instanceof HtmlShadowElement) {
            asShadow(newNextInsertion)._previousInsertion = this;
        }
    }

    /**
     * Updates the given last insertion to this shadow element. (Internal use only)
     * @since 10.0.0
     */
    public void updateLastInsertion(Component newLastInsertion) {
        _lastInsertion = newLastInsertion;
    }

    /**
     * Returns the first component of its insertion range.
     */
    public Component getFirstInsertion() {
        return _firstInsertion;
    }

    /**
     * Returns the last component of its insertion range.
     */
    public Component getLastInsertion() {
        return _lastInsertion;
    }

    public void setShadowHost(Component host, Component insertBefore) {
        if (getParent() != null) {
            throw new UiException("As a shadow child cannot be a shadow root. [" + this + "]");
        }
        if (host == null) {
            throw new UiException(
                    "The shadow host cannot be null. [" + this + "], please use detach() method instead!.");
        }
        if (_host != null) {
            throw new UiException("The shadow element cannot change its host, if existed. [" + this + "]");
        }
        _host = host;
        onHostAttached(host);

        _nextInsertion = insertBefore;
        if (insertBefore != null) {
            _previousInsertion = insertBefore.getPreviousSibling();
        } else {
            List<ShadowElement> shadowRoots = ((ComponentCtrl) host).getShadowRoots();
            ShadowElement lastShadowElement = shadowRoots.isEmpty() ? null : shadowRoots.get(shadowRoots.size() - 1);
            Component prev = (Component) lastShadowElement;
            HtmlShadowElement prevOwner = asShadow(lastShadowElement);
            Component lastChild = host.getLastChild();

            if (prevOwner == null) {
                prev = lastChild;
            } else {
                switch (HtmlShadowElement.inRange(prevOwner, lastChild)) {
                case NEXT:
                case AFTER_NEXT:
                    prev = lastChild;
                    break;
                case UNKNOWN:
                    boolean skip = false;
                    for (ShadowElement se : shadowRoots) {
                        if (se == prevOwner)
                            break;

                        // we need to check if the lastChild is contained in a shadow already.
                        switch (HtmlShadowElement.inRange(asShadow(se), lastChild)) {
                        case UNKNOWN:
                            break;
                        default:
                            skip = true; // yes, we found it.
                        }
                        if (skip)
                            break;
                    }
                    if (!skip)
                        prev = lastChild;
                    break;
                default:
                    // prev is the lastShadowElement 
                }
            }
            _previousInsertion = prev;
            if (prev == lastShadowElement && prev != null) {
                prevOwner._nextInsertion = this;
            }
        }

        ((ComponentCtrl) host).addShadowRoot(this);
        final Desktop desktop = host.getDesktop();
        if (desktop != null) {
            desktop.getWebApp().getConfiguration().afterShadowAttached(this, host);
        } else {
            final ShadowElement se = this;
            ((ComponentCtrl) host).addCallback(AFTER_PAGE_ATTACHED, new Callback<Component>() {
                public void call(Component host) {
                    host.getDesktop().getWebApp().getConfiguration().afterShadowAttached(se, host);
                }
            });
        }
    }

    /**
     * Removes the relation points between shadow host and this shadow element.
     */
    public void detach() {
        Component prevhost = getShadowHostIfAny();
        if (_host != null) {
            ComponentCtrl host = (ComponentCtrl) _host;
            _host = null; // clear first to avoid endloop
            ((ComponentCtrl) host).removeShadowRoot(this);
            onHostDetached((Component) host);
        }
        setParent0(null);
        if (prevhost != null) {
            if (prevhost.getDesktop() != null)
                prevhost.getDesktop().getWebApp().getConfiguration().afterShadowDetached(this, prevhost);
            else {
                final ShadowElement se = this;
                ((ComponentCtrl) prevhost).addCallback(AFTER_PAGE_DETACHED, new Callback<Component>() {
                    public void call(Component host) {
                        host.getDesktop().getWebApp().getConfiguration().afterShadowDetached(se, host);
                    }
                });
            }
        }
    }

    public void setParent(Component parent) {
        Component host = getShadowHostIfAny();

        setParent0(parent);

        if (host == null)
            host = getShadowHostIfAny();

        if (host != null) {
            if (parent != null) {
                host.getDesktop().getWebApp().getConfiguration().afterShadowAttached(this, host);
            } else {
                host.getDesktop().getWebApp().getConfiguration().afterShadowDetached(this, host);
            }
        }
    }

    private void setParent0(Component parent) {
        if (_host != null && parent != null) {
            throw new UiException("As a shadow root cannot be a child of a shadow element.");
        }

        if (parent == null && _host == null) {
            // detach
            if (_firstInsertion != null) {
                setPrevInsertion(_firstInsertion, _previousInsertion); // resync
                setPrevInsertion(_nextInsertion, _lastInsertion); // resync
            } else {
                setPrevInsertion(_nextInsertion, _previousInsertion); // resync
            }
            _previousInsertion = null;
            _firstInsertion = null;
            _lastInsertion = null;
            _nextInsertion = null;
        }
        super.setParent(parent);
    }

    public void beforeParentChanged(Component parent) {
        if (parent != null) {
            if (!(parent instanceof ShadowElement))
                throw new UiException("Unsupported parent for shadow element: " + parent);
            if (_host != null) {
                throw new UiException("Unsupported parent for shadow root: " + this);
            }
        }
        super.beforeParentChanged(parent);
    }

    public void beforeChildAdded(Component child, Component refChild) {
        if (!(child instanceof ShadowElement))
            throw new UiException("Unsupported child for shadow element: " + child);
        if (refChild != null && !(refChild instanceof ShadowElement))
            throw new UiException("Unsupported refChild for shadow element: " + refChild);
        HtmlShadowElement seChild = (HtmlShadowElement) child;
        HtmlShadowElement seRefChild = (HtmlShadowElement) refChild;

        HtmlShadowElement lastChild = asShadow(getLastChild());
        if (lastChild != null) {
            if (refChild == null) {
                if (lastChild._nextInsertion != null) {
                    seChild._previousInsertion = lastChild._nextInsertion;
                    if (seChild._nextInsertion == lastChild._nextInsertion)// avoid circle reference
                        seChild._nextInsertion = null;
                } else {
                    lastChild._nextInsertion = child;
                    seChild._previousInsertion = lastChild;
                }
            } else {
                //throw new IllegalAccessError("not implemented yet");
                //                if (isAncestor(asShadow(seRefChild.getParent()), seChild)) {

                // sync child's insertion
                Component previousInsertion = seChild.getPreviousInsertion();
                Component nextInsertion = seChild.getNextInsertion();
                setPrevInsertion(nextInsertion, previousInsertion);

                // sync refChild's insertion                
                previousInsertion = seRefChild.getPreviousInsertion();
                setPrevInsertion(seRefChild, seChild);
                setPrevInsertion(seChild, previousInsertion);
            }
        } else if (_lastInsertion != null) {
            if (refChild != null) {
                throw new IllegalStateException("Some logic wrong here.");
            } else {
                if (_lastInsertion instanceof HtmlShadowElement) {
                    setPrevInsertion(seChild, _lastInsertion);
                    if (seChild._nextInsertion == ((HtmlShadowElement) _lastInsertion)._nextInsertion)// avoid circle reference
                        seChild._nextInsertion = null;
                } else {
                    seChild._previousInsertion = _lastInsertion;
                }
            }
        }

        super.beforeChildAdded(child, refChild);
    }

    public void onChildAdded(org.zkoss.zk.ui.Component child) {
        super.onChildAdded(child);
        HtmlShadowElement childSE = asShadow(child);
        stretchRange(childSE._firstInsertion, childSE._lastInsertion);
    }

    // no need to handle if the children range is included.
    // For example,
    //         A => {B => {0,1}, 2}
    //        So A's range is 0~2, if B is removed.
    //         
    //    public void onChildRemoved(org.zkoss.zk.ui.Component child) {
    //        super.onChildRemoved(child);
    //        HtmlShadowElement childSE = asShadow(child);
    //        shrinkRange(childSE._firstInsertion, childSE._lastInsertion);
    //    }

    private Map<Component, Integer> getIndexMap(Component host) {
        Map<Component, Integer> distributedIndexInfo = (Map<Component, Integer>) ShadowElementsCtrl
                .getDistributedIndexInfo(host);
        if (distributedIndexInfo == null) {
            throw new IllegalStateException("Distributed index map cannot be null! [" + this + "]");
        }
        return distributedIndexInfo;
    }

    // unsupported Component methods
    public void invalidate() {
        throw new UnsupportedOperationException(
                "Unsupported for shadow element's invalidation, please use getShadowHost().invalidate() instead.");
    }

    private Map<Component, Integer> fillUpIndexMap(Component first, Component last) {
        if (first == null) // last will be null too 
            return getIndexMap(getShadowHostIfAny());
        Component parent = first.getParent();
        if (parent == null)
            throw new UiException("The insertion point cannot be null: " + first);
        List<Component> children = parent.getChildren();
        Map<Component, Integer> indexMap = getIndexMap(parent);
        // reuse map
        Integer integer = indexMap.get(first);
        if (integer != null) {
            if (indexMap.containsKey(last))
                return indexMap; //nothing to fill up
        }
        int i = 0;

        for (Iterator<Component> it = children.iterator(); it.hasNext(); i++) {
            Component next = it.next();
            if (indexMap.isEmpty()) {
                if (first == next) {
                    indexMap.put(next, i);
                }
            } else {
                indexMap.put(next, i);
                if (next == last) {
                    break;
                }
            }
        }
        return indexMap;
    }

    @SuppressWarnings("unused")
    private int[] getInsertionIndex(Component firstChild, Component lastChild, Map<Component, Integer> indexMap) {
        if (indexMap == null) {
            indexMap = fillUpIndexMap(firstChild, lastChild);
            return new int[] { indexMap.get(firstChild), indexMap.get(lastChild) };
        } else {
            Integer start = indexMap.get(firstChild), end = indexMap.get(lastChild);
            if (start == null || end == null) // refill
                indexMap = fillUpIndexMap(firstChild, lastChild);
            start = indexMap.get(firstChild);
            end = indexMap.get(lastChild);
            return new int[] { start, end };
        }
    }

    protected void stretchRange(Component firstChild, Component lastChild) {
        if (firstChild != null) { // has children
            boolean isEdge = false;
            if (_firstInsertion == null) { // init
                _firstInsertion = firstChild;
                _lastInsertion = lastChild;
                isEdge = true;
            } else {
                Map<Component, Integer> indexMap = fillUpIndexMap(firstChild, lastChild);
                int[] childIndex = getInsertionIndex(firstChild, lastChild, indexMap);
                int[] selfIndex = getInsertionIndex(_firstInsertion, _lastInsertion, indexMap);

                if (childIndex[0] < selfIndex[0]) { // left edge changed
                    isEdge = true;
                    _firstInsertion = firstChild;
                }
                if (selfIndex[1] < childIndex[1]) { // right edge changed
                    isEdge = true;
                    _lastInsertion = lastChild;
                }
            }
            if (isEdge && getParent() != null) {
                asShadow(getParent()).stretchRange(firstChild, lastChild);
            }
        }
    }

    protected void shrinkRange(Component firstChild, Component lastChild) {
        if (firstChild != null) { // has children
            boolean isEdge = false;
            if (firstChild == _firstInsertion) { // cut edge
                if (lastChild == _lastInsertion) { // cut all
                    _firstInsertion = _lastInsertion = null;
                } else {
                    // shrink from the start
                    _firstInsertion = lastChild.getNextSibling();
                }
                isEdge = true;
            } else if (lastChild == _lastInsertion) {
                isEdge = true;

                // shrink from the end
                _lastInsertion = _lastInsertion.getPreviousSibling();
            }
            if (isEdge && getParent() != null) {
                asShadow(getParent()).shrinkRange(firstChild, lastChild);
            }
        }
    }

    //Cloneable//
    public Object clone() {
        final HtmlShadowElement clone = (HtmlShadowElement) super.clone();

        clone._previousInsertion = _previousInsertion;
        clone._firstInsertion = _firstInsertion;
        clone._lastInsertion = _lastInsertion;
        clone._nextInsertion = _nextInsertion;
        return clone;
    }

    protected void initClone(AbstractComponent cloneHost) {
        if (_previousInsertion != null) {
            if (_previousInsertion instanceof ShadowElement) {
                _previousInsertion = (Component) cloneHost.getShadowRoots().get(((ComponentCtrl) _host).getShadowRoots().indexOf(_previousInsertion));
            } else {
                _previousInsertion = cloneHost.getChildren().get(_host.getChildren().indexOf(_previousInsertion));
            }
        }

        if (_firstInsertion != null) {
            _firstInsertion = cloneHost.getChildren().get(_host.getChildren().indexOf(_firstInsertion));
        }

        if (_lastInsertion != null) {
            _lastInsertion = cloneHost.getChildren().get(_host.getChildren().indexOf(_lastInsertion));
        }

        if (_nextInsertion != null) {
            if (_nextInsertion instanceof ShadowElement) {
                _nextInsertion = (Component) cloneHost.getShadowRoots().get(((ComponentCtrl) _host).getShadowRoots().indexOf(_nextInsertion));
            } else {
                _nextInsertion = cloneHost.getChildren().get(_host.getChildren().indexOf(_nextInsertion));
            }
        }
        _host = cloneHost;
    }

    public Component getShadowHost() {
        return _host;
    }

    /**
     * Creates the distributed children after apply dynamic properties
     * <p>
     * If a shadow element is created by ZK loader, this method is invoked
     * automatically. Developers rarely need to invoke this method.
     * <p>
     * Default: it invokes {@link #compose} to compose the shadow element.
     * <p>
     * The method will invoke the following methods in order.
     * <ol>
     * <li>Check if {@link #isEffective()} to be true.</li>
     * <li>If true, invokes {@link #compose} method to create the distributed
     * children, otherwise, nothing happened.</li>
     * </ol>
     * <p>
     * Instead of overriding this method, it is suggested to override
     * {@link #compose}, since all other methods depend on {@link #compose}
     * (rather than {@link #afterCompose}).
     */
    public void afterCompose() {
        if (!_afterComposed) { // don't do it twice, if it has a child.
            _afterComposed = true;
            if (isEffective() && _firstInsertion == null) {
                Component host = getShadowHostIfAny();
                if (host == null)
                    throw new UiException("Host cannot be null [" + this + "]");
                Object shadowInfo = ShadowElementsCtrl.getCurrentInfo();
                try {
                    ShadowElementsCtrl.setCurrentInfo(this);
                    compose(host);
                } finally {
                    ShadowElementsCtrl.setCurrentInfo(shadowInfo);
                }
                Execution exec = Executions.getCurrent();
                if (exec != null) {
                    String key = "org.zkoss.zk.ui.HttmlShadowelement" + host.getUuid();
                    if (!exec.hasAttribute(key)) {
                        exec.setAttribute(key, Boolean.TRUE);

                        // put it to the end of all events
                        Events.postEvent(-250000, new Event(ON_REBUILD_SHADOW_TREE_LATER, host));
                    }
                }

            }
        }
    }

    protected static void setPrevInsertion(Component target, Component prevInsertion) {
        if (target == prevInsertion)
            return; // do nothing

        if (target instanceof HtmlShadowElement) {
            asShadow(target)._previousInsertion = prevInsertion;
        }

        if (prevInsertion instanceof HtmlShadowElement) {
            asShadow(prevInsertion)._nextInsertion = target;
        }
    }

    /**
     * Merge the all sub-tree into the parent's insertions, unlike
     * {@link #appendChild(Component)}
     */
    protected void mergeSubTree() {
        List<HtmlShadowElement> children = getChildren();
        if (children == null || children.isEmpty())
            return; // nothing to do.
        if (_parent != null) {
            for (HtmlShadowElement child : new ArrayList<HtmlShadowElement>(children)) {
                Component previous = child._previousInsertion;
                Component next = child._nextInsertion;
                _parent.insertBefore(child, this);

                // resync the insertion of the child, if it has some comopnent sibling.
                if (previous != null && !(previous instanceof HtmlShadowElement)) {
                    Component newPrevious = child._previousInsertion;
                    setPrevInsertion(previous, newPrevious);
                    setPrevInsertion(child, previous);
                }
                if (next != null && !(next instanceof HtmlShadowElement)) {
                    Component newNext = child._nextInsertion;
                    setPrevInsertion(newNext, next);
                    setPrevInsertion(next, child);
                }

                if (_firstInsertion == child._firstInsertion)
                    _firstInsertion = null; // reset

                if (_lastInsertion == child._lastInsertion)
                    _lastInsertion = null; // reset
            }
        } else { // merge to host
            Component previous = _previousInsertion;
            HtmlShadowElement firstChild = children.get(0);
            if (previous != null) {
                Component newPrevious = firstChild._previousInsertion;
                if (newPrevious == null) {
                    setPrevInsertion(firstChild, previous);
                } else {
                    setPrevInsertion(newPrevious, previous);
                }
                _previousInsertion = null;
            }
            if (_firstInsertion == firstChild._firstInsertion || _firstInsertion == firstChild._previousInsertion)
                _firstInsertion = null; // reset
            HtmlShadowElement lastChild = children.get(children.size() - 1);
            Component newNext = lastChild._nextInsertion;
            if (_nextInsertion != null) {
                if (newNext == null) {
                    lastChild._nextInsertion = _nextInsertion;
                }
                if (_nextInsertion instanceof HtmlShadowElement) {
                    ((HtmlShadowElement) _nextInsertion)._previousInsertion = lastChild;
                }
                _nextInsertion = null;
            }
            if (_lastInsertion == lastChild._lastInsertion || _lastInsertion == lastChild._nextInsertion)
                _lastInsertion = null; // reset
            for (HtmlShadowElement child : new ArrayList<HtmlShadowElement>(children)) {
                child.mergeToHost(_host);
            }
        }
    }

    /**
     * Merge the host into the current shadow, unlike
     * {@link #setShadowHost(Component, Component)}
     * 
     * @param host
     */
    public boolean mergeToHost(Component host) {
        if (host == null)
            throw new UiException("The host cannot be null.");
        if (host == _host)
            return false; // nothing to do
        if (_parent == null)
            throw new UiException("The parent shadow cannot be null.");
        HtmlShadowElement oldParent = (HtmlShadowElement) _parent;

        HtmlShadowElement parent = (HtmlShadowElement) _parent;
        _parent = null;
        ((ComponentCtrl) host).addShadowRootBefore(this, (ShadowElement) parent);
        _host = host;

        // remove children reference
        ++parent._chdinf.modCntChd;
        --parent._chdinf.nChild;
        if (parent._chdinf.first == this)
            parent._chdinf.first = this._next;
        if (parent._chdinf.last == this) {
            if (parent._chdinf.first != null)
                parent._chdinf.last = this._prev;
            else
                parent._chdinf.last = null;
        }

        return true;
    }

    private void rebuildShadowTree() {
        Component hostIfAny = getShadowHostIfAny();
        Map<Component, Integer> oldCacheMap = getIndexCacheMap(hostIfAny);
        final boolean destroyCacheMap = oldCacheMap == null;
        try {
            if (destroyCacheMap) // the first caller
                initIndexCacheMap(hostIfAny);

            rebuildSubShadowTree();
        } finally {
            if (destroyCacheMap) // the first caller
                destroyIndexCacheMap(hostIfAny);
        }
    }

    /**
     * Rebuilds the shadow tree if the shadow element contains a dynamic value,
     * it should be alive, otherwise, it will be detached.
     * @throws ConcurrentModificationException if caller use the same collection,
     * it may throw this exception when merging sub-tree.
     */
    protected void rebuildSubShadowTree() {
        List<HtmlShadowElement> children = getChildren();
        for (HtmlShadowElement se : new ArrayList<HtmlShadowElement>(children)) {
            se.rebuildSubShadowTree();
        }
        if (!isDynamicValue()) {
            mergeSubTree();
            detach();
        }
    }

    /**
     * Returns whether the shadow element is effective
     */
    protected abstract boolean isEffective();

    /**
     * Composes the shadow element. It is called by {@link #afterCompose} if the
     * shadow host is not null. Otherwise, it will skip this method call.
     * <p>
     * The second invocation is ignored. If you want to recreate child
     * components, use {@link #recreate()} instead.
     * @param host the shadow host component, never null.
     */
    protected abstract void compose(Component host);

    public void beforeHostChildRemoved(Component child, int indexOfChild) {
        if (log.isDebugEnabled()) {
            log.debug("beforeHostChildRemoved " + child + ", in this shadow " + ShadowElementsCtrl.getCurrentInfo());
        }

        Object currentInfo = ShadowElementsCtrl.getCurrentInfo();
        if (currentInfo instanceof HtmlShadowElement) { // removed as my child in our control code
            if (currentInfo == this) {
                // do it at beginning. 
                adjustInsertionForRemove(this, child);

                boolean isEdge = false;
                Component oldFirst = _firstInsertion;
                Component oldLast = _lastInsertion;
                if (child == _firstInsertion) {
                    if (_firstInsertion == _lastInsertion) {
                        _firstInsertion = _lastInsertion = null;
                    } else {
                        _firstInsertion = child.getNextSibling();
                        oldLast = oldFirst; // only remove one by one
                    }
                    isEdge = true;
                } else if (child == _lastInsertion) {
                    isEdge = true;
                    _lastInsertion = child.getPreviousSibling();
                    oldFirst = oldLast; // only remove one by one
                }
                if (isEdge && getParent() != null) {
                    asShadow(getParent()).shrinkRange(oldFirst, oldLast);
                }
                // a callback
                onHostChildRemoved(child);
                return; // finish
            } else if (isAncestor(this, asShadow(currentInfo))) { // do only my descendent
                asShadow(currentInfo).beforeHostChildRemoved(child, indexOfChild);
                return; // finish
            }
        } else { // out of our control, we have to do Binary search for this  to
            // ZK-3549: should always update previous/next insertion first, before moving on to other conditions
            // resync index
            if (_previousInsertion == child) {
                setPrevInsertion(this, child.getPreviousSibling());
            } else if (_nextInsertion == child) {
                setPrevInsertion(child.getNextSibling(), this);
            }

            if (_firstInsertion == null)
                return; // out of our range;

            List<Component> children = child.getParent().getChildren();
            int selfFirstIndex = children.indexOf(_firstInsertion);
            if (indexOfChild < selfFirstIndex)
                return; // out of our range;

            Map<Component, Integer> indexMap = fillUpIndexMap(_firstInsertion, _lastInsertion);
            int[] selfIndex = getInsertionIndex(_firstInsertion, _lastInsertion,
                    fillUpIndexMap(_firstInsertion, _lastInsertion));
            if (selfIndex[1] < indexOfChild) return; // out of our range;

            HtmlShadowElement node = queryIntersectedShadowIfAny(indexOfChild, indexMap);
            if (node != null) {
                try {
                    ShadowElementsCtrl.setCurrentInfo(node);
                    asShadow(node).beforeHostChildRemoved(child, indexOfChild);
                } finally {
                    ShadowElementsCtrl.setCurrentInfo(currentInfo); // reset
                }
            }
        }

    }

    // as binary search for a segment tree.
    private HtmlShadowElement queryIntersectedShadowIfAny(int queryIndex, Map<Component, Integer> indexMap) {
        Object binarySearchSubTree = binarySearchSubTree(this, queryIndex, indexMap);
        if (binarySearchSubTree instanceof HtmlShadowElement)
            return asShadow(binarySearchSubTree);
        return null; // not found;
    }

    private class BinarySearchIterator {
        private HtmlShadowElement _subTree;
        private int _low, _high, _mid, _midChild, _queryIndex;

        public BinarySearchIterator(HtmlShadowElement subTree, int nChild, int queryIndex) {
            _subTree = subTree;
            _low = 0;
            _high = nChild - 1;
            _midChild = getMiddleIndex(_low, _high);
            _mid = _midChild;
            _queryIndex = queryIndex;
        }

        // return -1, not found;
        private int getMiddleIndex(int low, int high) {
            if (low > high)
                return -1;
            return (low + high) >>> 1;
        }

        public boolean hasNext() {
            return _low <= _high && _mid >= 0;
        }

        public HtmlShadowElement next() {
            return asShadow(_subTree.getChildren().get(_mid));
        }

        private void checkIndex() {
            int newMid = getMiddleIndex(_low, _high);
            if (_mid == newMid)
                _mid = -1; // nothing do to.
            else
                _midChild = _mid = newMid;
        }

        public void adjustCursor(Integer result) {
            final int queryResult = result.intValue();
            if (queryResult < 0) { // not found, find next
                if (_mid <= _low) {
                    _low = _midChild + 1; // not found and do it from right again.
                    checkIndex();
                } else {
                    _mid--;
                }
            } else if (queryResult > -1) { // find but not match
                if (_low == _mid && _mid == _high) {
                    _mid = -1; // not found to avoid dead loop
                } else {
                    if (queryResult < _queryIndex) {
                        _low = _mid + 1; // find from right
                    } else {
                        _high = _mid - 1; // find from left
                    }
                    checkIndex();
                }
            }
        }

    }

    private Object binarySearchSubTree(HtmlShadowElement subTree, int queryIndex, Map<Component, Integer> indexMap) {
        int startIndex, endIndex;
        if (subTree._firstInsertion == null) {
            return -1; // skip this;
        } else if ((startIndex = indexMap.get(subTree._firstInsertion)) > queryIndex) {
            return startIndex; // find from left
        } else if ((endIndex = indexMap.get(subTree._lastInsertion)) < queryIndex) {
            return endIndex; // find from right
        }

        final int nChild = subTree.nChild();

        if (nChild == 0)
            return subTree; // subTree is the intersection node.

        //        int midIndex = (endIndex - startIndex) >>> 1;
        BinarySearchIterator bsit = new BinarySearchIterator(subTree, nChild, queryIndex);
        while (bsit.hasNext()) {
            Object result = binarySearchSubTree(bsit.next(), queryIndex, indexMap);
            if (result instanceof Integer) {
                bsit.adjustCursor((Integer) result);
            } else {
                return result; // node is found.
            }
        }

        return subTree; // subTree is the intersection node.
    }

    /** Default: does nothing.
     * @see ComponentCtrl#onChildAdded
     */
    public void onHostChildRemoved(Component child) {
    }

    /** Default: does nothing.
     * @see ComponentCtrl#onChildAdded
     */
    public void onHostChildAdded(Component child) {
    }

    public void beforeHostParentChanged(Component parent) {
        if (log.isDebugEnabled()) {
            log.debug("beforeHostParentChanged " + parent + ", in this shadow " + ShadowElementsCtrl.getCurrentInfo());
        }
        if (parent == null) {
            ((ComponentCtrl) _host).removeShadowRoot(this);
        } else if (_host.getParent() == null) {
            onHostAttached(_host);
        }
    }

    public void beforeHostChildAdded(Component child, Component insertBefore, int indexOfInsertBefore) {
        if (log.isDebugEnabled()) {
            log.debug("beforeHostChildAdded " + child + ", " + insertBefore + ", in this shadow "
                    + ShadowElementsCtrl.getCurrentInfo());
        }

        Object currentInfo = ShadowElementsCtrl.getCurrentInfo();
        if (indexOfInsertBefore < 0) {
            if (currentInfo instanceof HtmlShadowElement) { // in our control
                HtmlShadowElement asShadow = asShadow(currentInfo);
                if (isAncestor(this, asShadow)) { // do only my descendent
                    Component lastChild = asShadow.getLastChild();
                    if (lastChild != null && asShadow(lastChild)._nextInsertion == null)
                        asShadow(lastChild)._nextInsertion = child;
                } else if (asShadow.getShadowHostIfAny() != getShadowHostIfAny()) { // not my ancestor, it may create by template and another host
                    if (_nextInsertion == null)
                        _nextInsertion = child;
                }
            } else { // out of our control
                if (_nextInsertion == null)
                    _nextInsertion = child;
            }
        } else { // special case
            Map<Component, Integer> indexMap = fillUpIndexMap(_firstInsertion, _lastInsertion);
            HtmlShadowElement node = queryIntersectedShadowIfAny(indexOfInsertBefore, indexMap);
            if (currentInfo instanceof HtmlShadowElement) { // in our control
                if (isAncestor(asShadow(currentInfo), node)) {
                    adjustInsertionForInsertBefore(node, child, insertBefore);
                } else if (!((HtmlShadowElement) currentInfo).getChildren().isEmpty()) { // adjust from currentInfo's first.
                    HtmlShadowElement currentShadow = asShadow(currentInfo);
                    asShadow(currentShadow.getLastChild())._nextInsertion = child;
                }
            } else if (node != null) {
                // check if the insertion is before the shadow root range,
                // if true, do nothing.
                if (this.getParent() != null || insertBefore != _firstInsertion) {
                    adjustInsertionForInsertBefore(node, child, insertBefore);
                } else { // in front of the shadow root.
                    _previousInsertion = child;
                }
            } else if (_nextInsertion == insertBefore) {
                //point _nextInsertion to the previous sibling, which is child
                _nextInsertion = child;
            }
        }
    }

    @SuppressWarnings("unchecked")
    protected static <T extends HtmlShadowElement> T asShadow(Object o) {
        return (T) o;
    }

    private boolean isAncestor(HtmlShadowElement parent, HtmlShadowElement child) {
        if (child == null)
            return false;
        if (parent == child)
            return true;
        return isAncestor(parent, asShadow(child.getParent()));
    }

    private boolean adjustInsertionForRemove(HtmlShadowElement se, Component removed) {
        Component old = null;
        Direction direction = inRange(se, removed);
        switch (direction) {
        case PREVIOUS:
            old = se._previousInsertion;
            if (old != null) {
                HtmlShadowElement previousSibling = asShadow(se.getPreviousSibling());
                if (previousSibling != null) { // se is not the first shadow element
                    if (previousSibling._nextInsertion == old) {
                        previousSibling._nextInsertion = se;
                        se._previousInsertion = previousSibling;
                    } else { // some children between two shadows
                        se._previousInsertion = old.getPreviousSibling();
                    }
                } else { // se is the first shadow element
                    HtmlShadowElement parentSe = asShadow(se.getParent());

                    // Update it when the following conditions:
                    // 1. se is the root tree, update it directly.
                    // 2. check if the old is not the first insertion of parent, that
                    // means still has some children in front of the se. 
                    if (parentSe == null || parentSe._firstInsertion != old) {
                        se._previousInsertion = old.getPreviousSibling();
                    } else {
                        se._previousInsertion = null;
                    }
                }
                return true;
            }
        case NEXT:
            old = se._nextInsertion;
            if (old != null) {
                HtmlShadowElement nextSibling = asShadow(se.getNextSibling());
                if (nextSibling != null) { // se is not the last shadow element
                    if (nextSibling._previousInsertion == old) {
                        nextSibling._previousInsertion = se;
                        se._nextInsertion = nextSibling;
                    } else { // some children between two shadows
                        se._nextInsertion = old.getNextSibling();
                    }
                } else { // se is the last shadow element
                    HtmlShadowElement parentSe = asShadow(se.getParent());

                    // Update it when the following conditions:
                    // 1. se is the root tree, update it directly.
                    // 2. check if the old is not the last insertion of parent, that
                    // means still has some children at the end of the se. 
                    if (parentSe == null || parentSe._lastInsertion != old) {
                        se._nextInsertion = old.getPreviousSibling();
                    } else {
                        se._nextInsertion = null;
                    }
                }
                return true;
            }
        case IN_RANGE: // check children
        case FIRST:
        case LAST:
            List<HtmlShadowElement> children = se.getChildren();
            if (!children.isEmpty()) {
                for (HtmlShadowElement aChildren : children) {
                    if (adjustInsertionForRemove(aChildren, removed))
                        return true;
                }
            } else { // Bug ZK-2837
                if (direction == Direction.FIRST) {
                    se.shrinkRange(se._firstInsertion, se._firstInsertion);
                    return true;
                } else if (direction == Direction.LAST) {
                    se.shrinkRange(se._lastInsertion, se._lastInsertion);
                    return true;
                }
            }
        default:
            return false;
        }
    }

    private boolean adjustInsertionForInsertBefore(HtmlShadowElement se, Component target, Component insertBefore) {
        Component old = null;
        Direction direction = inRange(se, insertBefore);
        switch (direction) {
        case PREVIOUS:
            old = se._previousInsertion;
            se._previousInsertion = target;
            if (old instanceof HtmlShadowElement) {
                asShadow(old)._nextInsertion = target;
            }
            return true;
        case NEXT:
            old = se._nextInsertion;
            se._nextInsertion = target;
            if (old instanceof HtmlShadowElement) {
                asShadow(old)._previousInsertion = target;
            }
            return true;
        case IN_RANGE: // check children
        case FIRST:
        case LAST:
            List<HtmlShadowElement> children = se.getChildren();
            if (children.isEmpty()) {
                if (direction == Direction.FIRST) {
                    // update previous sibling
                    old = se._previousInsertion;
                    se._previousInsertion = target;
                    if (old instanceof HtmlShadowElement) {
                        asShadow(old)._nextInsertion = target;
                    }
                    return true;

                }
            } else {
                for (HtmlShadowElement aChildren : children) {
                    if (adjustInsertionForInsertBefore(aChildren, target, insertBefore))
                        return true;
                }
            }
        default:
            return false;
        }
    }

    /**
     * A help class for an insertion direction.
     * @author jumperchen
     */
    public enum Direction {
        /**
         * It indicates the direction of the target is inserted before the previous insertion
         */
        BEFORE_PREVIOUS,
        /**
         * It indicates the direction of the target is the same as the previous insertion
         */
        PREVIOUS,
        /**
         * It indicates the direction of the target is the same as the first insertion
         */
        FIRST,
        /**
         * It indicates the direction of the target is inserted in its descendant insertion range
         */
        IN_RANGE,
        /**
         * It indicates the direction of the target is the same as the last insertion
         */
        LAST,
        /**
         * It indicates the direction of the target is the same as the next insertion
         */
        NEXT,
        /**
         * It indicates the direction of the target is inserted after the next insertion
         */
        AFTER_NEXT,
        /**
         * It cannot indicate the direction of the target where it should be inserted.
         */
        UNKNOWN
    }

    public static int getIndex(ShadowElement owner, Component insertion, Map<Component, Integer> cacheMap) {
        if (insertion == null)
            return -1;
        if (insertion.getParent() == null) {
            if (owner == null) {
                throw new IllegalStateException("The insertion cannot be orphan" + insertion);
            } else {
                if (insertion instanceof HtmlShadowElement && ((HtmlShadowElement) insertion).getShadowHost() != null) {
                    return -1;
                } else {
                    throw new IllegalStateException(
                            "The insertion [" + insertion + "] of the shadow [" + owner + "] cannot be orphan");
                }
            }
        }
        if (insertion instanceof ShadowElement)
            return -1; // cannot compare component with shadow
        Integer result = cacheMap.get(insertion);
        if (result != null)
            return result;
        int i = 0;
        int matched = -1;

        for (Iterator<Component> it = insertion.getParent().getChildren().iterator(); it.hasNext(); i++) {
            Component next = it.next();
            cacheMap.put(next, i);
            if (next == insertion) {
                matched = i;
            }
        }
        return matched;
    }

    /**
     * Returns the direction of the target component according to the given shadow element.
     * @param se the shadow element
     * @param target the target to check.
     */
    public static Direction inRange(HtmlShadowElement se, Component target) {
        Component hostIfAny = se.getShadowHostIfAny();
        Map<Component, Integer> oldCacheMap = se.getIndexCacheMap(hostIfAny);
        final boolean destroyCacheMap = oldCacheMap == null;
        try {
            // cache the index.
            if (destroyCacheMap)
                oldCacheMap = se.initIndexCacheMap(hostIfAny);

            int targetIndex = getIndex(null, target, oldCacheMap);
            int prev = getIndex(se, se.getPreviousInsertion(), oldCacheMap);
            int first = getIndex(se, se.getFirstInsertion(), oldCacheMap);
            int last = getIndex(se, se.getLastInsertion(), oldCacheMap);
            int next = getIndex(se, se.getNextInsertion(), oldCacheMap);
            if (targetIndex == prev) {
                return Direction.PREVIOUS;
            } else if (targetIndex == first) {
                return Direction.FIRST;
            } else if (targetIndex == last) {
                return Direction.LAST;
            } else if (targetIndex == next) {
                return Direction.NEXT;
            } else if (targetIndex > first && targetIndex < last) {
                return Direction.IN_RANGE;
            } else if (prev > -1) {
                return targetIndex - prev > 0 ? Direction.AFTER_NEXT : Direction.BEFORE_PREVIOUS;
            } else if (first > -1) {
                return targetIndex - first > 0 ? Direction.AFTER_NEXT : Direction.BEFORE_PREVIOUS;
            } else if (next > -1) {
                return targetIndex - next > 0 ? Direction.AFTER_NEXT : Direction.BEFORE_PREVIOUS;
            } else if (last > -1) {
                return targetIndex - last > 0 ? Direction.AFTER_NEXT : Direction.BEFORE_PREVIOUS;
            } else {
                return Direction.UNKNOWN;
            }
        } finally {
            if (destroyCacheMap)
                se.destroyIndexCacheMap(hostIfAny);
        }
    }

    public void afterHostChildAdded(Component child, int indexOfChild) {
        if (log.isDebugEnabled()) {
            log.debug("afterHostChildAdded " + child + ", in this shadow " + ShadowElementsCtrl.getCurrentInfo());
        }
        Object currentInfo = ShadowElementsCtrl.getCurrentInfo();
        if (currentInfo instanceof HtmlShadowElement) { // added as my child in our control code
            if (currentInfo == this) {
                boolean isEdge = false;
                if (_firstInsertion == null) { // initial range
                    _firstInsertion = _lastInsertion = child;
                    isEdge = true;
                } else if (_firstInsertion != child && _lastInsertion != child) {
                    int[] selfIndex = getInsertionIndex(_firstInsertion, _lastInsertion,
                            fillUpIndexMap(_firstInsertion, _lastInsertion));
                    if (indexOfChild < selfIndex[0]) {
                        _firstInsertion = child;
                        isEdge = true;
                    } else if (indexOfChild > selfIndex[1]) {
                        _lastInsertion = child;
                        isEdge = true;
                    }

                }
                if (getParent() != null && isEdge) {
                    asShadow(getParent()).stretchRange(_firstInsertion, _lastInsertion);
                }

                // a callback
                onHostChildAdded(child);
                return; // finish
            } else if (isAncestor(this, asShadow(currentInfo))) { // do only my descendent
                asShadow(currentInfo).afterHostChildAdded(child, indexOfChild);
                return; // finish
            }
        } else { // out of our control, we have to do Binary search for this  to
            if (_firstInsertion == null)
                return; // out of our range;

            List<Component> children = child.getParent().getChildren();
            int insertIndex = children.indexOf(child);
            int selfFirstIndex = children.indexOf(_firstInsertion);
            if (insertIndex < selfFirstIndex)
                return; // out of our range;

            Map<Component, Integer> indexMap = fillUpIndexMap(_firstInsertion, _lastInsertion);
            int[] selfIndex = getInsertionIndex(_firstInsertion, _lastInsertion, indexMap);
            if (selfIndex[1] < insertIndex)
                return; // out of our range;

            HtmlShadowElement node = queryIntersectedShadowIfAny(insertIndex, indexMap);
            if (node != null) {
                try {
                    ShadowElementsCtrl.setCurrentInfo(node);
                    asShadow(node).afterHostChildAdded(child, indexOfChild);
                } finally {
                    ShadowElementsCtrl.setCurrentInfo(currentInfo); // reset
                }
            }
        }
    }

    public void afterHostChildRemoved(Component child) {
        if (log.isDebugEnabled()) {
            log.debug("afterHostChildRemoved " + child + ", in this shadow " + ShadowElementsCtrl.getCurrentInfo());
        }
    }

    /** Detaches all child components and then recreate them by use of
     * {@link #compose}.
     */
    public void recreate() {
        if (_afterComposed) { // execute after composed
            clearChildren();
            _afterComposed = false; // reset
            afterCompose();
        }
    }

    public void clearChildren() {
        AbstractComponent hostIfAny = (AbstractComponent) getShadowHostIfAny();
        try {
            if (hostIfAny != null) {
                hostIfAny.disableHostChanged();
            }
            if (getFirstChild() != null) {
                removeChildren(getFirstChild());
            }

            Component firstInsertion = _firstInsertion;
            Component lastInsertion = _lastInsertion;
            Component nextInsertion = lastInsertion != null ? lastInsertion.getNextSibling() : null;
            Component previousInsertion = firstInsertion != null ? firstInsertion.getPreviousSibling() : null;
            if (_firstInsertion != null) {
                for (Component next = _firstInsertion, end =
                     _lastInsertion != null
                             ? _lastInsertion.getNextSibling() : null;
                     next != end;) {
                    Component tmp = next.getNextSibling();
                    next.detach();
                    next = tmp;
                }
            }
            if (hostIfAny != null) {
                Component parent = getParent();
                while (parent != null) {
                    HtmlShadowElement parentSe = (HtmlShadowElement) parent;
                    if (parentSe._firstInsertion == firstInsertion) {
                        if (parentSe._lastInsertion == lastInsertion) {
                            parentSe._firstInsertion = parentSe._lastInsertion = null; // cut all
                        } else {
                            parentSe._firstInsertion = nextInsertion;
                        }
                    } else if (parentSe._lastInsertion == lastInsertion) {
                        parentSe._lastInsertion = Optional.ofNullable(
                                        parentSe._lastInsertion.getPreviousSibling())
                                .orElse(previousInsertion);
                    }
                    parent = parent.getParent();
                }
            }
            _firstInsertion = _lastInsertion = null;
        } finally {
            if (hostIfAny != null) {
                hostIfAny.enableHostChanged();
            }
        }
    }

    private void removeChildren(Component firstChild) {
        for (Component next = firstChild; next != null;) {
            // recursively remove all children, depth first
            if (next.getFirstChild() != null) {
                removeChildren(next.getFirstChild());
            }
            Component tmp = next.getNextSibling();
            ((HtmlShadowElement) next).removeFromParent();
            next = tmp;
        }
    }

    protected void removeFromParent() {
        if (_parent == null) {
            throw new UiException("The parent shadow cannot be null.");
        }
        _parent.removeChild(this);
    }

    public Component getShadowHostIfAny() {
        Component parent = this;
        while (parent.getParent() != null) {
            parent = parent.getParent();
        }
        return ((ShadowElement) parent).getShadowHost();
    }

    @SuppressWarnings("checkstyle:MethodName")
    private final int nDChild() {
        if (_firstInsertion != null) {
            int size = 1;
            Component next = _firstInsertion;
            while (next != _lastInsertion) {
                size++;
                next = next.getNextSibling();
            }
            return size;
        }
        return 0;
    }

    @SuppressWarnings("unchecked")
    public <T extends Component> List<T> getDistributedChildren() {
        final Component shadowHostIfAny = getShadowHostIfAny();

        return new AbstractSequentialList<T>() {
            @SuppressWarnings("unchecked")
            public ListIterator<T> listIterator(int index) {
                return (ListIterator<T>) new ChildIter((AbstractComponent) shadowHostIfAny, index);
            }

            public int size() {
                return nDChild();
            }

            public T get(int index) {
                try {
                    return listIterator(index).next();
                } catch (NoSuchElementException exc) {
                    throw new IndexOutOfBoundsException("Index: " + index);
                }
            }

        };
    }

    private class ChildIter implements ListIterator<Component> {
        private AbstractComponent _p, _lastRet;
        private int _j;
        private int _modCntSnap;
        private AbstractComponent host;

        private ChildIter(AbstractComponent host, int index) {
            this.host = host;
            int nChild;
            if (index < 0 || index > (nChild = nDChild()))
                throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + nDChild());

            if (index < (nChild >> 1)) {
                _p = (AbstractComponent) HtmlShadowElement.this._firstInsertion;
                for (_j = 0; _j < index; _j++)
                    _p = _p._next;
            } else {
                _p = null; //means the end of the list
                for (_j = nChild; _j > index; _j--)
                    _p = _p != null ? _p._prev : (AbstractComponent) HtmlShadowElement.this._lastInsertion;
            }
            _modCntSnap = host.modCntChd();
        }

        public boolean hasNext() {
            checkComodification();
            return _j < nDChild();
        }

        public Component next() {
            if (_j >= nDChild())
                throw new java.util.NoSuchElementException();
            checkComodification();

            _lastRet = _p;
            _p = _p._next;
            _j++;
            return _lastRet;
        }

        public boolean hasPrevious() {
            checkComodification();
            return _j > 0;
        }

        public Component previous() {
            if (_j <= 0)
                throw new java.util.NoSuchElementException();
            checkComodification();

            _lastRet = _p = _p != null ? _p._prev : (AbstractComponent) HtmlShadowElement.this._lastInsertion;
            _j--;
            return _lastRet;
        }

        private void checkComodification() {
            if (host.modCntChd() != _modCntSnap)
                throw new java.util.ConcurrentModificationException();
        }

        public int nextIndex() {
            return _j;
        }

        public int previousIndex() {
            return _j - 1;
        }

        public void add(Component newChild) {
            throw new UnsupportedOperationException("add Component");
        }

        public void remove() {
            if (_lastRet == null)
                throw new IllegalStateException();
            checkComodification();

            if (_p == _lastRet)
                _p = _lastRet._next; //previous was called
            else
                --_j; //next was called

            host.removeChild(_lastRet);

            _lastRet = null;
            ++_modCntSnap;
        }

        public void set(Component o) {
            throw new UnsupportedOperationException("set Component");
            //Possible to implement this but confusing to developers
            //if o has the same parent (since we have to move)
        }
    }

    // refer to AnnotateBinderHelper.INIT_ANNO
    protected static final String INIT_ANNO = "init";
    // refer to AnnotateBinderHelper.BIND_ANNO
    protected static final String BIND_ANNO = "bind";
    // refer to AnnotateBinderHelper.BIND_ANNO
    protected static final String LOAD_ANNO = "load";
    // refer to AnnotateBinderHelper.SAVE_ANNO
    protected static final String SAVE_ANNO = "save";
    // refer to AnnotateBinderHelper.REFERENCE_ANNO
    protected static final String REFERENCE_ANNO = "ref";
    // refer to BinderImpl.BINDER
    protected static final String BINDER = "$BINDER$";

    /**
     * Returns whether the property name contains with a dynamic value.
     */
    protected boolean isDynamicValue(String propName) {
        final ComponentCtrl compCtrl = this;
        Collection<Annotation> annos = compCtrl.getAnnotations(propName);
        if (!annos.isEmpty()) {
            for (Annotation anno : annos) {
                final String annoName = anno.getName();
                if (annoName.equals(BIND_ANNO) || annoName.equals(LOAD_ANNO) || annoName.equals(SAVE_ANNO)
                        || annoName.equals(REFERENCE_ANNO) || annoName.equals(INIT_ANNO)) {
                    return true;
                }
            }
        }
        return false;
    }

    protected Boolean _dynamicValue;

    public boolean isDynamicValue() {
        if (_dynamicValue == null) {
            final ComponentCtrl ctrl = this;
            List<String> props = ctrl.getAnnotatedProperties();
            if (props != null) {
                for (String prop : props) {
                    if (isDynamicValue(prop)) {
                        _dynamicValue = true;
                        break;
                    }
                }
                if (_dynamicValue == null)
                    _dynamicValue = Boolean.FALSE;
            }
        }
        return _dynamicValue == null ? Boolean.FALSE : _dynamicValue.booleanValue();
    }

    /**
     * Sets whether the shadow element contains a dynamic value, if true means the
     * shadow element cannot be destroyed after evaluated, if false it will detect
     * its attribute automatically.
     * <p>Default: false (auto detection)</p>
     * @since 8.0.1
     */
    public void setDynamicValue(boolean dynamicValue) {
        if (dynamicValue)
            _dynamicValue = dynamicValue;
        else
            _dynamicValue = null; // null means it's 'auto' detection.
    }

    //-- Object --//
    public String toString() {
        final String clsnm = getClass().getSimpleName();
        if (_host == null) {
            if (getParent() != null)
                return getParent() + " -> <" + clsnm + "@" + (getParent().getChildren().indexOf(this)) + ">";
            else
                return '<' + clsnm + '>';
        }
        ComponentCtrl host = (ComponentCtrl) _host;
        return "<" + clsnm + "@" + host.getShadowRoots().indexOf(this) + " (" + _host + ")>";
    }

    @Override
    protected void updateSubBindingAnnotationCount(int diff) {
        for (AbstractComponent node = this; node != null;) {
            setSubBindingAnnotationCount(diff, node);
            AbstractComponent p = (AbstractComponent) node.getParent();
            if (p != null) {
                node = p;
            } else {
                node = (AbstractComponent) ((HtmlShadowElement) node).getShadowHost();
                if (node != null)
                    node.updateSubBindingAnnotationCount(diff);
                break;
            }
        }
    }

    /**
     * Internal use
     * @since 10.0.0
     */
    public Map<Component, Integer> initIndexCacheMap(Component host) {
        return super.initIndexCacheMap(host);
    }

    /**
     * Internal use
     * @since 10.0.0
     */
    public Map<Component, Integer> getIndexCacheMap(Component host) {
        return super.getIndexCacheMap(host);
    }

    /**
     * Internal use
     * @since 10.0.0
     */
    public void destroyIndexCacheMap(Component host) {
        super.destroyIndexCacheMap(host);
    }
}