zk/src/main/java/org/zkoss/zk/ui/HtmlShadowElement.java
/** 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);
}
}