All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zkoss.zk.ui.AbstractComponent Maven / Gradle / Ivy

There is a newer version: 10.0.0-jakarta
Show newest version
/* AbstractComponent.java

	Purpose:
		
	Description:
		
	History:
		Mon May 30 21:49:42     2005, Created by tomyeh

Copyright (C) 2005 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zk.ui;

import static org.zkoss.lang.Generics.cast;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.AbstractSequentialList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

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

import org.zkoss.io.Serializables;
import org.zkoss.json.JavaScriptValue;
import org.zkoss.lang.Classes;
import org.zkoss.lang.Library;
import org.zkoss.lang.Objects;
import org.zkoss.lang.Strings;
import org.zkoss.util.CollectionsX;
import org.zkoss.util.Converter;
import org.zkoss.zk.au.AuRequest;
import org.zkoss.zk.au.AuResponse;
import org.zkoss.zk.au.AuService;
import org.zkoss.zk.au.out.AuClientInfo;
import org.zkoss.zk.au.out.AuEcho;
import org.zkoss.zk.ui.event.Deferrable;
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.ForwardEvent;
import org.zkoss.zk.ui.ext.Macro;
import org.zkoss.zk.ui.ext.NonFellow;
import org.zkoss.zk.ui.ext.RawId;
import org.zkoss.zk.ui.ext.Scope;
import org.zkoss.zk.ui.ext.ScopeListener;
import org.zkoss.zk.ui.ext.render.Cropper;
import org.zkoss.zk.ui.impl.SimpleScope;
import org.zkoss.zk.ui.impl.Utils;
import org.zkoss.zk.ui.metainfo.Annotation;
import org.zkoss.zk.ui.metainfo.AnnotationMap;
import org.zkoss.zk.ui.metainfo.ComponentDefinition;
import org.zkoss.zk.ui.metainfo.ComponentInfo;
import org.zkoss.zk.ui.metainfo.DefinitionNotFoundException;
import org.zkoss.zk.ui.metainfo.EventHandler;
import org.zkoss.zk.ui.metainfo.EventHandlerMap;
import org.zkoss.zk.ui.metainfo.LanguageDefinition;
import org.zkoss.zk.ui.metainfo.PageDefinition;
import org.zkoss.zk.ui.metainfo.ShadowInfo;
import org.zkoss.zk.ui.metainfo.ZScript;
import org.zkoss.zk.ui.select.Selectors;
import org.zkoss.zk.ui.sys.Attributes;
import org.zkoss.zk.ui.sys.BooleanPropertyAccess;
import org.zkoss.zk.ui.sys.ComponentCtrl;
import org.zkoss.zk.ui.sys.ComponentRedraws;
import org.zkoss.zk.ui.sys.ComponentsCtrl;
import org.zkoss.zk.ui.sys.ContentRenderer;
import org.zkoss.zk.ui.sys.DesktopCtrl;
import org.zkoss.zk.ui.sys.EventListenerMap;
import org.zkoss.zk.ui.sys.ExecutionCtrl;
import org.zkoss.zk.ui.sys.HtmlPageRenders;
import org.zkoss.zk.ui.sys.JSCumulativeContentRenderer;
import org.zkoss.zk.ui.sys.JsContentRenderer;
import org.zkoss.zk.ui.sys.Names;
import org.zkoss.zk.ui.sys.PropertiesRenderer;
import org.zkoss.zk.ui.sys.PropertyAccess;
import org.zkoss.zk.ui.sys.ShadowElementsCtrl;
import org.zkoss.zk.ui.sys.StringPropertyAccess;
import org.zkoss.zk.ui.sys.StubComponent;
import org.zkoss.zk.ui.sys.StubsComponent;
import org.zkoss.zk.ui.sys.UiEngine;
import org.zkoss.zk.ui.sys.WebAppCtrl;
import org.zkoss.zk.ui.util.Callback;
import org.zkoss.zk.ui.util.ComponentActivationListener;
import org.zkoss.zk.ui.util.ComponentCloneListener;
import org.zkoss.zk.ui.util.ComponentSerializationListener;
import org.zkoss.zk.ui.util.Template;

/**
 * A skeletal implementation of {@link Component}.
 *
 * @author tomyeh
 */
public class AbstractComponent implements Component, ComponentCtrl, java.io.Serializable {
	private static final Logger log = LoggerFactory.getLogger(AbstractComponent.class);
	private static final long serialVersionUID = 20100719L;

	/** Map(Class, Map(String name, Integer flags)). */
	private static final Map, Map> _clientEvents = new ConcurrentHashMap, Map>(
			128);
	private static final String DEFAULT = Impls.DEFAULT;
	private static final String ANONYMOUS_ID = "z__i";
	/** Used to generate an anonymous ID. */
	private static int _anonymousId;
	/** Library property key. */
	private static final String AUTO_REMOVE_NULL = "org.zkoss.zk.ui.component.autoRemoveNullAttribute.enabled";

	/*package*/ transient Page _page;
	private String _id = "";
	private String _uuid;
	private transient ComponentDefinition _def;

	/*package*/ transient AbstractComponent _parent; //called by HtmlShadowElement
	/** The next sibling. */
	/*package*/ transient AbstractComponent _next;
	/** The previous sibling. */
	/*package*/ transient AbstractComponent _prev;
	/** ChildInfo: use a class (rather than multiple member) to save footprint */
	/*package*/ transient ChildInfo _chdinf;
	/** AuxInfo: use a class (rather than multiple member) to save footprint */
	private AuxInfo _auxinf;

	private Map _shadowIdMap; //to speed up id only shadow selector

	/** Constructs a component with auto-generated ID.
	 * @since 3.0.7 (becomes public)
	 */
	public AbstractComponent() {
		final String mold = getDefaultMold(getClass());
		if (mold != null && mold.length() > 0 && !DEFAULT.equals(mold))
			initAuxInfo().mold = mold;

		final Execution exec = Executions.getCurrent();
		final Object curInfo = ComponentsCtrl.getCurrentInfo();
		if (curInfo != null) {
			ComponentsCtrl.setCurrentInfo((ComponentInfo) null); //to avoid mis-use
			if (curInfo instanceof ComponentInfo) {
				final ComponentInfo compInfo = (ComponentInfo) curInfo;
				_def = compInfo.getComponentDefinition();
				addSharedAnnotationMap(_def.getAnnotationMap());
				addSharedAnnotationMap(compInfo.getAnnotationMap());
				//F80 - store subtree's binder annotation count
				if (compInfo.hasBindingAnnotation())
					enableBindingAnnotation();
			} else if (curInfo instanceof ShadowInfo) {
				final ShadowInfo compInfo = (ShadowInfo) curInfo;
				_def = compInfo.getComponentDefinition();
				addSharedAnnotationMap(_def.getAnnotationMap());
				addSharedAnnotationMap(compInfo.getAnnotationMap());
				//F80 - store subtree's binder annotation count
				if (compInfo.hasBindingAnnotation())
					enableBindingAnnotation();
			} else {
				_def = (ComponentDefinition) curInfo;
				addSharedAnnotationMap(_def.getAnnotationMap());
			}
		} else {
			_def = Impls.getDefinition(exec, getClass());
			if (_def != null)
				addSharedAnnotationMap(_def.getAnnotationMap());
			else
				_def = ComponentsCtrl.DUMMY;
		}

		addSharedAnnotationMap(Impls.getClassAnnotationMap(this.getClass()));

		if (this instanceof IdSpace)
			initAuxInfo().spaceInfo = new SpaceInfo();

		_def.applyAttributes(this);
		//		if (log.isDebugEnabled()) log.debug("Create comp: "+this);
	}

	/** Constructs a dummy component that is not associated
	 * with any component definition.
	 * @param useless an useless argument (it is ignored but used
	 * to distinguish the default constructor)
	 * @since 6.0.0
	 */
	protected AbstractComponent(boolean useless) {
		_def = ComponentsCtrl.DUMMY;
	}

	/** The default implementation for {@link #getChildren}.
	 * It is suggested to extend this class if you want to override
	 * {@link #getChildren} to instantiate your own instance.
	 * @since 3.5.1
	 */
	protected class Children extends AbstractSequentialList {
		public int size() {
			return nChild();
		}

		public ListIterator listIterator(int index) {
			return new ChildIter(index);
		}
	}

	/** Adds to the ID spaces, if any, when ID is changed.
	 * Caller has to make sure the uniqueness (and not auto id).
	 */
	private static void addToIdSpaces(final Component comp) {
		final String compId = comp.getId();
		if (comp instanceof NonFellow || isAutoId(compId))
			return; //nothing to do

		if (comp instanceof IdSpace)
			((AbstractComponent) comp).bindToIdSpace(comp);

		addFellow(comp, getSpaceOwnerOfParent(comp));
	}

	private static void addFellow(Component comp, IdSpace owner) {
		if (owner instanceof Component)
			((AbstractComponent) owner).bindToIdSpace(comp);
		else if (owner instanceof Page)
			((AbstractPage) owner).addFellow(comp);
		if (owner == null && comp instanceof ShadowElement)
			addToShadowIdMap(comp);
	}

	private static void removeFellow(Component comp, IdSpace owner) {
		if (owner instanceof Component)
			((AbstractComponent) owner).unbindFromIdSpace(comp.getId());
		else if (owner instanceof Page)
			((AbstractPage) owner).removeFellow(comp);
		if (owner == null && comp instanceof ShadowElement)
			removeFromShadowIdMap(comp);
	}

	private static IdSpace getSpaceOwnerOfParent(Component comp) {
		final Component parent = comp.getParent();
		return parent != null ? spaceOwnerNoVirtual(parent)
				: //ignore virtual IdSpace
				comp.getPage();
	}

	/** Removes from the ID spaces, if any, when ID is changed. */
	private static void removeFromIdSpaces(final Component comp) {
		final String compId = comp.getId();
		if (comp instanceof NonFellow || isAutoId(compId))
			return; //nothing to do

		if (comp instanceof IdSpace)
			((AbstractComponent) comp).unbindFromIdSpace(compId);

		removeFellow(comp, getSpaceOwnerOfParent(comp));
	}

	/** Checks the uniqueness in ID space when changing ID. */
	private static void checkIdSpaces(final AbstractComponent comp, String newId) {
		if (comp instanceof NonFellow)
			return; //no need to check

		if (comp instanceof IdSpace && comp._auxinf.spaceInfo.fellows.containsKey(newId))
			throw new UiException("Not unique in the ID space of " + comp);

		final IdSpace is = getSpaceOwnerOfParent(comp);
		if (is instanceof Component) {
			if (((AbstractComponent) is)._auxinf.spaceInfo.fellows.containsKey(newId))
				throw new UiException("Not unique in ID space " + is + ": " + newId);
		} else if (is != null) {
			if (is.hasFellow(newId))
				throw new UiException("Not unique in ID space " + is + ": " + newId);
		}
	}

	/*package*/ static boolean isAutoId(String compId) {
		return compId.length() == 0;
	}

	/** Adds its descendants to the ID space when parent or page is changed,
	 * excluding comp.
	 */
	private static void addToIdSpacesDown(Component comp) {
		addToIdSpacesDown(comp, getSpaceOwnerOfParent(comp));
	}

	private static void addToIdSpacesDown(Component comp, IdSpace owner) {
		if (!(comp instanceof NonFellow) && !isAutoId(comp.getId()))
			addFellow(comp, owner);

		if (!(comp instanceof IdSpace))
			for (AbstractComponent ac = (AbstractComponent) comp.getFirstChild(); ac != null; ac = ac._next)
				addToIdSpacesDown(ac, owner); //recursive

		((AbstractComponent) comp).notifyIdSpaceChanged(owner);
	}

	private void notifyIdSpaceChanged(IdSpace newIdSpace) {
		if (_auxinf != null && _auxinf.attrs != null)
			_auxinf.attrs.notifyIdSpaceChanged(newIdSpace);
	}

	/** Adds its descendants to the ID space when parent or page is changed,
	 * excluding comp.
	 */
	private static void removeFromIdSpacesDown(Component comp) {
		removeFromIdSpacesDown(comp, getSpaceOwnerOfParent(comp));
	}

	private static void removeFromIdSpacesDown(Component comp, IdSpace owner) {
		if (!(comp instanceof NonFellow) && !isAutoId(comp.getId()))
			removeFellow(comp, owner);

		if (!(comp instanceof IdSpace))
			for (AbstractComponent ac = (AbstractComponent) comp.getFirstChild(); ac != null; ac = ac._next)
				removeFromIdSpacesDown(ac, owner); //recursive

		((AbstractComponent) comp).notifyIdSpaceChanged(null);
	}

	/** Checks the uniqueness in ID space when changing parent. */
	private static void checkIdSpacesDown(Component comp, Component newparent) {
		final IdSpace is = spaceOwnerNoVirtual(newparent); //exclude virtual IdSpace
		//for checking, it is better NOT to ignore virtual IdSpace
		//but, for better performance, we don't.
		if (is != null)
			checkIdSpacesDown(comp, is);
	}

	private static void checkIdSpacesDown(Component comp, IdSpace owner) {
		final String compId = comp.getId();
		if (!(comp instanceof NonFellow) && !isAutoId(compId)
				&& (owner instanceof Component
						? ((AbstractComponent) owner)._auxinf.spaceInfo.fellows.containsKey(compId)
						: owner.hasFellow(compId)))
			throw new UiException("Not unique in the ID space of " + owner + ": " + compId);
		if (!(comp instanceof IdSpace))
			for (AbstractComponent ac = (AbstractComponent) comp.getFirstChild(); ac != null; ac = ac._next)
				checkIdSpacesDown(ac, owner); //recursive
	}

	/** Bind comp to this ID space (owned by this component).
	 * Called only if IdSpace is implemented.
	 * comp's ID must be unique (and not auto id)
	 */
	private void bindToIdSpace(Component comp) {
		_auxinf.spaceInfo.fellows.put(comp.getId(), comp);
	}

	/** Unbind comp from this ID space (owned by this component).
	 * Called only if IdSpace is implemented.
	 */
	private void unbindFromIdSpace(String compId) {
		_auxinf.spaceInfo.fellows.remove(compId);
	}

	//-- Extra utilities --//
	/** Returns the UI engine based on {@link #_page}'s getDesktop().
	 * Don't call this method when _page is null.
	 */
	private UiEngine getAttachedUiEngine() {
		return ((WebAppCtrl) _page.getDesktop().getWebApp()).getUiEngine();
	}

	/** Returns the UI engine of the current execution, or null
	 * if no current execution.
	 */
	private UiEngine getCurrentUiEngine() {
		final Execution exec = Executions.getCurrent();
		return exec != null ? ((WebAppCtrl) exec.getDesktop().getWebApp()).getUiEngine() : null;
	}

	//-- Component --//
	public Page getPage() {
		return _page;
	}

	public Desktop getDesktop() {
		return _page != null ? _page.getDesktop() : null;
	}

	public void setPage(Page page) {
		if (page != _page)
			setPageBefore(page, null); //append
	}

	public void setPageBefore(Page page, Component refRoot) {
		beforeComponentPageChanged(page);
		if (refRoot != null && (page == null || refRoot.getParent() != null || refRoot.getPage() != page))
			refRoot = null;
		if (refRoot != null /*&& refRoot.getPage() == page (checked)*/
				&& (refRoot == this || refRoot == _next))
			return; //nothing to do

		if (_parent != null)
			throw new UiException("Only the parent of a root component can be changed: " + this);

		final Page oldpg = _page;
		final boolean samepg = page == _page;
		if (!samepg) {
			if (page != null) {
				if (_page == null)
					clearVirtualIdSpace(); //clear if being attached
				else if (_page.getDesktop() != page.getDesktop())
					throw new UiException("The new page must be in the same desktop: " + page);
				//Not allow developers to access two desktops simultaneously
				checkIdSpacesDown(this, page);

				//No need to check UUID since checkIdSpacesDown covers it
				//-- a page is an ID space
			} else { //detach from a page
				checkDetach(_page);
			}

			if (_page != null)
				removeFromIdSpacesDown(this);
		}

		addMoved(_parent, _page, page); //Not depends on UUID
		if (!samepg)
			setPage0(page); //UUID might be changed here
		if (page != null && (samepg || refRoot != null))
			((AbstractPage) page).moveRoot(this, refRoot);

		if (!samepg && _page != null)
			addToIdSpacesDown(this);

		afterComponentPageChanged(page, oldpg);
	}

	/** Checks whether it is OK to detach the specified page.
	 * @param page the page to detach (never null).
	 */
	private static void checkDetach(Page page) {
		final Execution exec = Executions.getCurrent();
		if (exec == null)
			throw new UiException("You cannot access a desktop other than an event listener");
		if (page.getDesktop() != exec.getDesktop())
			throw new UiException("You cannot access components belong to other desktop");
	}

	/** Called when this component is moved from the specified parent
	 * and/or page to the new page.
	 *
	 * 

Default: it notifies {@link UiEngine} to update the component * at the client (usually remove-and-add). * *

It is designed to let derived classes overriding this method * to disable this update. However, you rarely need to override it. * One possible but rare case: the component's * visual part at the client updates the visual representation * at the client and then notify the component at the server * to update its children accordingly. In this case, it is redundant * if we ask UI Engine to send the updates to client. * * @param oldparent the parent before moved. * The new parent can be found by calling {@link #getParent}. * @param oldpg the parent before moved. * @param newpg the new page. {@link #getPage} might return * the old page. */ protected void addMoved(Component oldparent, Page oldpg, Page newpg) { final Desktop dt; if (oldpg != null) dt = oldpg.getDesktop(); else if (newpg != null) dt = newpg.getDesktop(); else return; ((WebAppCtrl) dt.getWebApp()).getUiEngine().addMoved(this, oldparent, oldpg, newpg); } /** Set the page without fixing IdSpace */ private void setPage0(Page page) { if (page == _page) return; //nothing changed //assert _parent == null || _parent.getPage() == page; //detach final boolean bRoot = _parent == null; boolean resetUuid = false; if (_page != null) { if (bRoot) ((AbstractPage) _page).removeRoot(this); if (page == null && ((DesktopCtrl) _page.getDesktop()).removeComponent(this, true) && !(this instanceof StubComponent)) //Bug ZK-1452: don't need to reset StubComponent's uuid resetUuid = true; //recycled (so reset it -- refer to DesktopImpl for reason) } final Page oldpage = _page; _page = page; if (_page != null) { if (bRoot) ((AbstractPage) _page).addRoot(this); //Not depends on uuid final Desktop desktop = _page.getDesktop(); if (oldpage == null) { if (_uuid == null || _uuid.startsWith(ANONYMOUS_ID) || desktop.getComponentByUuidIfAny(_uuid) != null) _uuid = nextUuid(desktop); ((DesktopCtrl) desktop).addComponent(this); //depends on uuid } onPageAttached(_page, oldpage); } else { onPageDetached(oldpage); } //process all children recursively for (AbstractComponent p = (AbstractComponent) getFirstChild(); p != null; p = p._next) p.setPage0(page); //recursive if (resetUuid) _uuid = null; //reset it after everything is done since some tool might depend on it } private String nextUuid(Desktop desktop) { for (int count = 0;;) { String uuid = ((DesktopCtrl) desktop).getNextUuid(this); if (desktop.getComponentByUuidIfAny(uuid) == null) return uuid; if (++count > 10000) throw new UiException( "It took too much time to look for unique UUID. Please check the implementation of IdGenerator."); } } public String getId() { return _id; } public void setId(String id) { if (id == null) id = ""; if (!id.equals(_id)) { final boolean rawId = this instanceof RawId; String newUuid = null; if (rawId) newUuid = id; if (id.length() > 0) { if (Names.isReserved(id)) throw new UiException( "Invalid ID: " + id + ". Cause: reserved words not allowed: " + Names.getReservedNames()); if (rawId && _page != null) { final Component c = _page.getDesktop().getComponentByUuidIfAny(newUuid); if (c != null && c != this) throw new UiException("Replicated UUID is not allowed for " + getClass() + ": " + newUuid); } checkIdSpaces(this, id); } removeFromIdSpaces(this); if (rawId) { //we have to change UUID if (_page != null) { //called before uuid is changed final Desktop dt = _page.getDesktop(); ((DesktopCtrl) dt).removeComponent(this, false); if (newUuid.length() == 0) newUuid = nextUuid(dt); if (!Objects.equals(_uuid, newUuid)) getAttachedUiEngine().addUuidChanged(this); } _id = id; _uuid = newUuid; if (_page != null) { ((DesktopCtrl) _page.getDesktop()).addComponent(this); } } else { _id = id; } addToIdSpaces(this); smartUpdate("id", _id); } } public String getUuid() { if (_uuid == null) { Execution exec = null; // ZK-2606: if parent has _page, child is in the same component tree for (Component comp = this; comp != null; comp = comp.getParent()) { if (comp.getPage() != null) { exec = Executions.getCurrent(); break; } } _uuid = exec == null ? ANONYMOUS_ID + _anonymousId++ : nextUuid(exec.getDesktop()); //OK to race for _anonymousId (since ok to be the same) } return _uuid; } public IdSpace getSpaceOwner() { return spaceOwner(this, false); } private static IdSpace spaceOwnerNoVirtual(Component p) { return spaceOwner(p, true); } private static IdSpace spaceOwner(Component p, boolean ignoreVirtualIS) { Component top; do { if (p instanceof IdSpace) return (IdSpace) p; top = p; } while ((p = p.getParent()) != null); final AbstractComponent ac = (AbstractComponent) top; return ac._page != null ? ac._page : ignoreVirtualIS ? null : ac.getVirtualIdSpace(); } /** Returns the UI object that will serve as a space owner. * Unlike {@link #spaceOwner}, it will return the top component if * it is a virtual IdSpace. Furthermore, clearVirtualIdSpace will be called * before returned. It is used only by {@link #setParent}. */ private static Object spaceController(Component p) { Component top; do { if (p instanceof IdSpace) return p; top = p; } while ((p = p.getParent()) != null); final AbstractComponent ac = (AbstractComponent) top; if (ac._page != null) return ac._page; ac.clearVirtualIdSpace(); return ac; //yes, return the top (virtual ID space) } /** Called only if this is root and has no page. The caller has to make sure it. */ private IdSpace getVirtualIdSpace() { if (_chdinf != null) { if (_chdinf.vispace == null) _chdinf.vispace = new VirtualIdSpace(this); return _chdinf.vispace; } return new VirtualIdSpace(this); //no need to cache since it is fast and small (since no child) } /** It must be called if a root component without page and doesn't implement * idspace: 1) is added to a page, 2) is added to a component, * 3) add or remove a new descendant (not under another IdSpace). * It is harmless if called redundantly. */ private void clearVirtualIdSpace() { if (_chdinf != null) _chdinf.vispace = null; } public boolean hasFellow(String compId) { if (this instanceof IdSpace) return _auxinf.spaceInfo.fellows.containsKey(compId); final IdSpace idspace = getSpaceOwner(); return idspace != null && idspace.hasFellow(compId); } public boolean hasFellow(String compId, boolean recurse) { return getFellowIfAny(compId, recurse) != null; } public Component getFellow(String compId) throws ComponentNotFoundException { if (this instanceof IdSpace) { final Component comp = _auxinf.spaceInfo.fellows.get(compId); if (comp == null) throw new ComponentNotFoundException("Fellow component not found: " + compId); return comp; } final IdSpace idspace = getSpaceOwner(); if (idspace == null) throw new ComponentNotFoundException("This component doesn't belong to any ID space: " + this); return idspace.getFellow(compId); } public Component getFellow(String compId, boolean recurse) throws ComponentNotFoundException { final Component comp = getFellowIfAny(compId, recurse); if (comp == null) throw new ComponentNotFoundException("Fellow component not found: " + compId); return comp; } public Component getFellowIfAny(String compId) { if (this instanceof IdSpace) return _auxinf.spaceInfo.fellows.get(compId); final IdSpace idspace = getSpaceOwner(); return idspace == null ? null : idspace.getFellowIfAny(compId); } public Component getFellowIfAny(String compId, boolean recurse) { if (!recurse) return getFellowIfAny(compId); for (IdSpace idspace = getSpaceOwner(); idspace != null;) { Component f = idspace.getFellowIfAny(compId); if (f != null) return f; idspace = Components.getParentIdSpace(idspace); } return null; } public Collection getFellows() { if (this instanceof IdSpace) return Collections.unmodifiableCollection(_auxinf.spaceInfo.fellows.values()); final IdSpace idspace = getSpaceOwner(); if (idspace != null) return idspace.getFellows(); return Collections.emptyList(); } public Component getNextSibling() { return _next; } public Component getPreviousSibling() { return _prev; } public Component getFirstChild() { return _chdinf != null ? _chdinf.first : null; } public Component getLastChild() { return _chdinf != null ? _chdinf.last : null; } /** Returns the number of children. */ @SuppressWarnings("checkstyle:MethodName") /*package*/ final int nChild() { //called by HtmlNativeComponent return _chdinf != null ? _chdinf.nChild : 0; } /** Returns the number of children. It assumes _chdinf not null. */ @SuppressWarnings("checkstyle:MethodName") /*package*/ final void nChild(AbstractComponent first, AbstractComponent last, int nChild) { _chdinf.first = first; _chdinf.last = last; _chdinf.nChild = nChild; for (; first != null; first = first._next) first._parent = this; } /*package*/ int modCntChd() { // called by HtmlShadowElement return _chdinf != null ? _chdinf.modCntChd : 0; } public String setWidgetListener(String evtnm, String script) { if (evtnm == null) throw new IllegalArgumentException(); final String old; if (script != null) { if (initAuxInfo().wgtlsns == null) _auxinf.wgtlsns = new LinkedHashMap(4); old = _auxinf.wgtlsns.put(evtnm, script); } else old = _auxinf != null && _auxinf.wgtlsns != null ? _auxinf.wgtlsns.remove(evtnm) : null; if (!Objects.equals(script, old)) smartUpdateWidgetListener(evtnm, script); return old; } public String getWidgetListener(String evtnm) { return _auxinf != null && _auxinf.wgtlsns != null ? _auxinf.wgtlsns.get(evtnm) : null; } public Set getWidgetListenerNames() { if (_auxinf != null && _auxinf.wgtlsns != null) return _auxinf.wgtlsns.keySet(); return Collections.emptySet(); } public String setWidgetOverride(String name, String script) { if (name == null) throw new IllegalArgumentException(); final String old; if (script != null) { if (initAuxInfo().wgtovds == null) _auxinf.wgtovds = new LinkedHashMap(4); old = _auxinf.wgtovds.put(name, script); } else old = _auxinf != null && _auxinf.wgtovds != null ? _auxinf.wgtovds.remove(name) : null; if (!Objects.equals(script, old)) smartUpdateWidgetOverride(name, script); return old; } public String getWidgetOverride(String name) { return _auxinf != null && _auxinf.wgtovds != null ? _auxinf.wgtovds.get(name) : null; } public Set getWidgetOverrideNames() { if (_auxinf != null && _auxinf.wgtovds != null) return _auxinf.wgtovds.keySet(); return Collections.emptySet(); } public String setWidgetAttribute(String name, String value) { return setClientAttribute(name, value); } public String setClientAttribute(String name, String value) { if (name == null) throw new IllegalArgumentException(); final String old; if (value != null) { if (initAuxInfo().domattrs == null) _auxinf.domattrs = new LinkedHashMap(4); old = _auxinf.domattrs.put(name, value); } else old = _auxinf != null && _auxinf.domattrs != null ? _auxinf.domattrs.remove(name) : null; return old; } public String getWidgetAttribute(String name) { return getClientAttribute(name); } public String getClientAttribute(String name) { return _auxinf != null && _auxinf.domattrs != null ? _auxinf.domattrs.get(name) : null; } public Set getWidgetAttributeNames() { if (_auxinf.domattrs != null) return _auxinf.domattrs.keySet(); return Collections.emptySet(); } public String setClientDataAttribute(String name, String value) { String old = setClientAttribute("data-" + name, value); if (old != null) invalidate(); return old; } public String getClientDataAttribute(String name) { return getClientAttribute("data-" + name); } public Map getAttributes(int scope) { switch (scope) { case SPACE_SCOPE: if (this instanceof IdSpace) return getAttributes(); final IdSpace idspace = getSpaceOwner(); if (idspace != null) return idspace.getAttributes(); return Collections.emptyMap(); case PAGE_SCOPE: if (_page != null) return _page.getAttributes(); return Collections.emptyMap(); case DESKTOP_SCOPE: if (_page != null) return _page.getDesktop().getAttributes(); return Collections.emptyMap(); case SESSION_SCOPE: if (_page != null) return _page.getDesktop().getSession().getAttributes(); return Collections.emptyMap(); case APPLICATION_SCOPE: if (_page != null) return _page.getDesktop().getWebApp().getAttributes(); return Collections.emptyMap(); case COMPONENT_SCOPE: return getAttributes(); case REQUEST_SCOPE: final Execution exec = getExecution(); if (exec != null) return exec.getAttributes(); //fall thru default: return Collections.emptyMap(); } } public Map getAttributes() { return attrs().getAttributes(); } private SimpleScope attrs() { if (initAuxInfo().attrs == null) _auxinf.attrs = new SimpleScope(this); return _auxinf.attrs; } private Execution getExecution() { return _page != null ? _page.getDesktop().getExecution() : Executions.getCurrent(); } public Object getAttribute(String name, int scope) { return getAttributes(scope).get(name); } public Object getAttribute(String name) { return _auxinf != null && _auxinf.attrs != null ? _auxinf.attrs.getAttribute(name) : null; } public Object getAttribute(String name, boolean recurse) { Object val = getAttribute(name); if (val != null || !recurse || hasAttribute(name)) return val; if (_parent != null) return _parent.getAttribute(name, true); if (_page != null) return _page.getAttribute(name, true); return null; } public boolean hasAttribute(String name, int scope) { return getAttributes(scope).containsKey(name); } public boolean hasAttribute(String name) { return _auxinf != null && _auxinf.attrs != null && _auxinf.attrs.hasAttribute(name); } public boolean hasAttribute(String name, boolean recurse) { if (hasAttribute(name)) return true; if (recurse) { if (_parent != null) return _parent.hasAttribute(name, true); if (_page != null) return _page.hasAttribute(name, true); } return false; } // Bug ZK-2789: allow null attribute values. If auto remove lib prop is // enabled, then set null attribute value = remove attribute public Object setAttribute(String name, Object value, int scope) { if (value == null && Boolean.parseBoolean(Library.getProperty(AUTO_REMOVE_NULL))) { // null value + old method = remove attribute return removeAttribute(name, scope); } // either 1. null value + new method or 2. non null value + old/new method final Map attrs = getAttributes(scope); if (attrs == Collections.EMPTY_MAP) throw new IllegalStateException("This component, " + this + ", doesn't belong to the " + Components.scopeToString(scope) + " scope"); return attrs.put(name, value); } // Bug ZK-2789: allow null attribute values. If auto remove lib prop is enabled, // then set null attribute value = remove attribute public Object setAttribute(String name, Object value) { if (value == null && shallAutoRemove()) { // null value + old method = remove attribute return removeAttribute(name); } // either 1. null value + new method or 2. non null value + old/new method return attrs().setAttribute(name, value); } public Object setAttribute(String name, Object value, boolean recurse) { if (recurse && !hasAttribute(name)) { if (_parent != null) { if (_parent.hasAttribute(name, true)) return _parent.setAttribute(name, value, true); } else if (_page != null) { if (_page.hasAttribute(name, true)) return _page.setAttribute(name, value, true); } } return setAttribute(name, value); } public Object removeAttribute(String name, int scope) { final Map attrs = getAttributes(scope); if (attrs == Collections.emptyMap()) throw new IllegalStateException("This component doesn't belong to any ID space: " + this); return attrs.remove(name); } public Object removeAttribute(String name) { return _auxinf != null && _auxinf.attrs != null ? _auxinf.attrs.removeAttribute(name) : null; } public Object removeAttribute(String name, boolean recurse) { if (recurse && !hasAttribute(name)) { if (_parent != null) { if (_parent.hasAttribute(name, true)) return _parent.removeAttribute(name, true); } else if (_page != null) { if (_page.hasAttribute(name, true)) return _page.removeAttribute(name, true); } return null; } return removeAttribute(name); } private Boolean hasInited = null; private boolean shallAutoRemove() { if (hasInited == null) { hasInited = Boolean.parseBoolean(Library.getProperty(AUTO_REMOVE_NULL)); } return hasInited.booleanValue(); } public Object getAttributeOrFellow(String name, boolean recurse) { Object val = getAttribute(name); if (val != null || hasAttribute(name)) return val; if (this instanceof IdSpace) { //fellow last val = getFellowIfAny(name); if (val != null) return val; } if (recurse) { if (_parent != null) return _parent.getAttributeOrFellow(name, true); if (_page != null) return _page.getAttributeOrFellow(name, true); if (this instanceof ShadowElement) { Component shadowHost = ((ShadowElement) this).getShadowHost(); if (shadowHost != null) return shadowHost.getAttributeOrFellow(name, true); } if (!(this instanceof IdSpace)) return getVirtualIdSpace().getFellowIfAny(name); } return null; } private boolean _variableSeeking = false; public Object getShadowVariable(String name, boolean recurse) { return getShadowVariable(this, name, recurse); } public Object getShadowVariable(Component baseChild, String name, boolean recurse) { try { _variableSeeking = true; return getShadowVariable0(baseChild, name, recurse); } finally { _variableSeeking = false; } } protected Object getShadowVariable0(Component baseChild, String name, boolean recurse) { try { _variableSeeking = true; Object val = getAttribute(name); if (val != null || hasAttribute(name)) return val; if (this instanceof ShadowElement) { // Bug fixed for the test case SimpleELResolverTest.java final Object value = ((ShadowElementCtrl) this).resolveVariable(null, name, recurse); if (value != null) return value; } else { ComponentCtrl ctrl = this; List shadowRoots = ctrl.getShadowRoots(); if (!shadowRoots.isEmpty()) { Map indexCacheMap = getIndexCacheMap(); try { if (indexCacheMap != null) { destroyIndexCacheMap(); // reset } initIndexCacheMap(); for (HtmlShadowElement shadow : shadowRoots) { if (shadow.getShadowHost() != baseChild) { switch (HtmlShadowElement.inRange(shadow, baseChild)) { case IN_RANGE: case FIRST: case LAST: HtmlShadowElement current = findNearestShadow(shadow, baseChild); return current.resolveVariable(baseChild, name, recurse); } } else { val = shadow.resolveVariable(baseChild, name, recurse); if (val != null) return val; } } } finally { ShadowElementsCtrl.setDistributedIndexInfo(indexCacheMap); } } } if (recurse) { if (_parent != null) return _parent.getShadowVariable0(this, name, recurse); if (this instanceof ShadowElement) { AbstractComponent shadowHost = (AbstractComponent) ((ShadowElement) this).getShadowHost(); if (shadowHost != null) { if (shadowHost._variableSeeking) { if (shadowHost.getParent() != null) { return ((AbstractComponent) shadowHost.getParent()).getShadowVariable0(shadowHost, name, recurse); } return null; // avoid deadloop } return ((AbstractComponent) shadowHost).getShadowVariable0(shadowHost, name, recurse); } } } return null; } finally { _variableSeeking = false; } } private HtmlShadowElement findNearestShadow(HtmlShadowElement current, Component baseChild) { List list = cast(current.getChildren()); for (ShadowElement sh : list) { if (sh instanceof HtmlShadowElement) { HtmlShadowElement shadow0 = (HtmlShadowElement) sh; switch (HtmlShadowElement.inRange(shadow0, baseChild)) { case IN_RANGE: case FIRST: case LAST: return findNearestShadow(shadow0, baseChild); } } } return current; } public boolean hasAttributeOrFellow(String name, boolean recurse) { if (hasAttribute(name) || (this instanceof IdSpace && hasFellow(name))) return true; if (recurse) { if (_parent != null) return _parent.hasAttributeOrFellow(name, true); if (_page != null) return _page.hasAttributeOrFellow(name, true); if (this instanceof ShadowElement) { Component shadowHost = ((ShadowElement) this).getShadowHost(); if (shadowHost != null) return shadowHost.hasAttributeOrFellow(name, true); } if (!(this instanceof IdSpace)) return getVirtualIdSpace().hasFellow(name); } return false; } public boolean addScopeListener(ScopeListener listener) { return attrs().addScopeListener(listener); } public boolean removeScopeListener(ScopeListener listener) { return attrs().removeScopeListener(listener); } public String getAutag() { return _auxinf != null ? _auxinf.autag : null; } public void setAutag(String tag) { if (tag != null && Strings.isEmpty(tag)) tag = null; if (!Objects.equals(_auxinf != null ? _auxinf.autag : null, tag)) { initAuxInfo().autag = tag; smartUpdate("autag", getAutag()); } } public Component getParent() { return _parent; } public void setParent(Component parent) { if (_parent == parent) return; //nothing changed checkParentChild(parent, this); //create _chdinf beforeParentChanged(parent); triggerBeforeHostParentChanged(parent); final boolean idSpaceChanged = (parent != null ? spaceController(parent) : null) != (_parent != null ? spaceController(_parent) : _page); clearVirtualIdSpace(); //clear since it is being added to another if (idSpaceChanged) removeFromIdSpacesDown(this); //call removeChild and clear _parent final AbstractComponent op = _parent; if (op != null) { if (!op._chdinf.inRemoving(this)) { op._chdinf.markRemoving(this, true); try { op.removeChild(this); //spec: call back removeChild } finally { op._chdinf.markRemoving(this, false); } } _parent = null; //op.removeChild assumes _parent not changed yet } else { if (_page != null) ((AbstractPage) _page).removeRoot(this); //Not depends on uuid } //call insertBefore and set _parent if (parent != null) { final AbstractComponent np = (AbstractComponent) parent; if (!np._chdinf.inAdding(this)) { np._chdinf.markAdding(this, true); try { np.insertBefore(this, null); //spec: call back inserBefore } finally { np._chdinf.markAdding(this, false); } } _parent = np; //np.insertBefore assumes _parent not changed yet } //if parent == null, assume no page at all (so no addRoot) //correct _page final Page newpg = _parent != null ? _parent.getPage() : null, oldpg = _page; addMoved(op, _page, newpg); //Not depends on UUID try { ComponentsCtrl.setRootParent(parent != null ? parent : op); setPage0(newpg); //UUID might be changed here } finally { ComponentsCtrl.setRootParent(null); } if (_auxinf != null && _auxinf.attrs != null) _auxinf.attrs.notifyParentChanged(_parent != null ? _parent : (Scope) _page); if (idSpaceChanged) addToIdSpacesDown(this); //called after setPage //call back UiLifeCycle afterComponentPageChanged(newpg, oldpg); if (newpg != null || oldpg != null) { final Desktop desktop = (oldpg != null ? oldpg : newpg).getDesktop(); if (desktop != null) { ((DesktopCtrl) desktop).afterComponentMoved(parent, this, op); desktop.getWebApp().getConfiguration().afterComponentMoved(parent, this, op); } } } private void beforeComponentPageChanged(Page page) { //ZK-1148, add a @destroy annotation method. if (page == null) WebApps.getCurrent().getConfiguration().invokeCallback("destroy", this); } private void afterComponentPageChanged(Page newpg, Page oldpg) { if (newpg == oldpg) return; final Desktop desktop = (oldpg != null ? oldpg : newpg).getDesktop(); if (desktop == null) return; //just in case //Note: if newpg and oldpg both non-null, they must be the same if (oldpg != null) { ((DesktopCtrl) desktop).afterComponentDetached(this, oldpg); desktop.getWebApp().getConfiguration().afterComponentDetached(this, oldpg); } else { ((DesktopCtrl) desktop).afterComponentAttached(this, newpg); desktop.getWebApp().getConfiguration().afterComponentAttached(this, newpg); } } /** * Checks the parent-child relation. * Notice it will create parent._chdinf * @param parent the parent (will-be). It may be null. * @param child the child (will-be). It cannot be null. */ private static void checkParentChild(Component parent, Component child) throws UiException { if (child == null) throw new UiException("Child cannot be null"); if (parent != null) { final AbstractComponent acp = (AbstractComponent) parent; if (acp.initChildInfo().inAdding(child)) return; //check only once if (Components.isAncestor(child, parent)) throw new UiException("A child cannot be a parent of its ancestor: " + child); if (!acp.isChildable()) throw new UiException("Child not allowed in " + parent.getClass().getName()); final Page parentpg = parent.getPage(), childpg = child.getPage(); if (parentpg != null && childpg != null && parentpg.getDesktop() != childpg.getDesktop()) throw new UiException("The parent and child must be in the same desktop: " + parent); final Component oldparent = child.getParent(); if (spaceOwnerNoVirtual(parent) != (oldparent != null ? spaceOwnerNoVirtual(oldparent) : childpg)) checkIdSpacesDown(child, parent); } else { final Page childpg = child.getPage(); if (childpg != null) checkDetach(childpg); } } public boolean insertBefore(Component newChild, Component refChild) { if ((newChild instanceof Macro) && ((Macro) newChild).isInline()) return ((Macro) newChild).setInlineParent(this, refChild); checkParentChild(this, newChild); ///create _chdinf if (refChild != null && refChild.getParent() != this) refChild = null; if (newChild == refChild) return false; //nothing changed (Listbox and other assumes this) beforeChildAdded(newChild, refChild); triggerBeforeHostChildAdded(newChild, refChild); final AbstractComponent nc = (AbstractComponent) newChild; final boolean moved = nc._parent == this; //moved in the same parent if (moved) { if (nc._next == refChild) return false; //nothing changed nc.addMoved(this, _page, _page); //detach from original place setNext(nc._prev, nc._next); setPrev(nc._next, nc._prev); } else { //new added //Note: call setParent to detach nc from old parent, if any, //before maintaining nc's _next, _prev... if (!_chdinf.inAdding(nc)) { _chdinf.markAdding(nc, true); try { nc.setParent(this); //spec: callback setParent } finally { _chdinf.markAdding(nc, false); } } else { nc._parent = this; //Set it since deriving class might assume parent is correct //after insertBefore. For example, Tabs.insertBefore(). // //However, we don't call setPage0 and other here, //since the codes will become too complex. //In other words, when super.insertBefore() returns in a //deriving class, _parent is correct but _page may or may not } } if (refChild != null) { final AbstractComponent ref = (AbstractComponent) refChild; setNext(nc, ref); setPrev(nc, ref._prev); setNext(ref._prev, nc); setPrev(ref, nc); } else { if (_chdinf.last == null) { _chdinf.first = _chdinf.last = nc; nc._next = nc._prev = null; } else { _chdinf.last._next = nc; nc._prev = _chdinf.last; nc._next = null; _chdinf.last = nc; } } ++_chdinf.modCntChd; if (!moved) { //new added ++_chdinf.nChild; onChildAdded(nc); triggerAfterHostChildAdded(nc); //F80 - store subtree's binder annotation count updateSubBindingAnnotationCount(nc.initAuxInfo().subAnnotCnt); } return true; } /** Set the next sibling of the given child. (this is a parent of comp). */ /*package*/ final void setNext(AbstractComponent comp, AbstractComponent next) { if (comp != null) comp._next = next; else _chdinf.first = next; } /** Set the previous sibling of the given child. (this is a parent of comp). */ /*package*/ final void setPrev(AbstractComponent comp, AbstractComponent prev) { if (comp != null) comp._prev = prev; else _chdinf.last = prev; } /** Increases the number of children. It assumes _chdinf not null. */ /*package*/ final void incNChild(int diff) { _chdinf.nChild += diff; } /** Replace the specified component with this component in * the component tree. In other words, the parent of the given * component will become the parent of this components, so * are siblings and children. Furthermore, comp will be detached * at the end. * *

Notice that the replacement won't change anything at the client. * It is the caller'job to maintain the consistency between the server * and the client. * *

This method is rarely used. * * @param comp the component. In this implementation it supports * only derived classes of {@link AbstractComponent}. * @param bFellow whether to add this component to the map of fellows * if it is assigned with an ID. If false, the component (comp) cannot * be retrieved back even with an ID (note: ID is always preserved). * @param bListener whether to retain the event listeners and handlers. * If true, the event listeners and handlers, if any, will be registered * to this stub component. In other words, the event will be processed. * However, it is a stub component, rather than the original one. * I means the event is the most generic format: an instance of * {@link org.zkoss.zk.ui.event.Event} (rather than MouseEvent or others). * @param bChildren whether to have the children of the given component.
* If false, this component won't have any children, and all UUID of children * reference back to this component.
* If true, the given component's children will belong to this component. * @exception IllegalStateException if this component has a parent, * sibling or child. * @since 6.0.0 */ protected void replace(Component comp, boolean bFellow, boolean bListener, boolean bChildren) { ((AbstractComponent) comp).replaceWith(this, bFellow, bListener, bChildren); } private final void replaceWith(AbstractComponent comp, boolean bFellow, boolean bListener, boolean bChildren) { if (this == comp || comp._parent != null || comp._next != null || comp._prev != null || comp._chdinf != null || comp._page != null) throw new IllegalStateException(); comp._def = _def; comp._uuid = _uuid; //remove this from the fellow map removeFromIdSpaces(this); //call before changing _parent... //fix parent/sibling link AbstractComponent p = comp._parent = _parent, q = comp._prev = _prev; if (q != null) q._next = comp; else if (p != null) p._chdinf.first = comp; q = comp._next = this._next; if (q != null) q._prev = comp; else if (p != null) p._chdinf.last = comp; _parent = _next = _prev = null; //fix the uuid-to-component map final Page page; if ((page = _page) != null) { comp._page = page; if (comp._parent == null) ((AbstractPage) page).onReplaced(this, comp); //call onReplaced instead addRoot/removeRoot final DesktopCtrl desktopCtrl = (DesktopCtrl) page.getDesktop(); desktopCtrl.mapComponent(_uuid, comp); _page = null; } //add comp to the fellow map comp._id = _id; if (bFellow) addToIdSpaces(comp); ///called after fixing comp._parent... if (_auxinf != null) comp._auxinf = _auxinf.cloneStub(comp, bListener); //fix children link; do it as the last step, //since StubsComponent.onChildrenMerge depends on _page if (_chdinf != null) if (bChildren) { for (p = _chdinf.first; p != null; p = p._next) p._parent = comp; comp._chdinf = _chdinf; _chdinf = null; } else if (comp instanceof StubsComponent) { //dirty but not worth to generalize it yet ((StubsComponent) comp).onChildrenMerged(this, bListener); } else if (page != null) { childrenMerged((DesktopCtrl) page.getDesktop(), _chdinf); } } private static void childrenMerged(DesktopCtrl desktopCtrl, ChildInfo chdinf) { if (chdinf != null) for (AbstractComponent p = chdinf.first; p != null; p = p._next) { desktopCtrl.removeComponent(p, false); //don't recycle it (since the client might hold them) childrenMerged(desktopCtrl, p._chdinf); } } /** Appends a child to the end of all children. * It calls {@link #insertBefore} with refChild to be null. * Derives cannot override this method, and they shall override * {@link #insertBefore} instead. */ public boolean appendChild(Component child) { //Yes, final; see below return insertBefore(child, null); //NOTE: we must go thru insertBefore //such that deriving is easy to override } public boolean removeChild(Component child) { final AbstractComponent oc = (AbstractComponent) child; if (oc._parent != this) return false; //nothing to do beforeChildRemoved(child); triggerBeforeHostChildRemoved(child); setNext(oc._prev, oc._next); setPrev(oc._next, oc._prev); oc._next = oc._prev = null; if (!_chdinf.inRemoving(oc)) { _chdinf.markRemoving(oc, true); try { oc.setParent(null); //spec: call back setParent } finally { _chdinf.markRemoving(oc, false); } } else { oc._parent = null; //Correct it since deriving class might assume parent is //correct after insertBefore() returns. //refer to insertBefore for more info. } //ZK-725: we can't remove _chdinf even if no child at all, //since this method might be called by ChildIter.remove() ++_chdinf.modCntChd; --_chdinf.nChild; onChildRemoved(child); //F80 - store subtree's binder annotation count updateSubBindingAnnotationCount(-oc.initAuxInfo().subAnnotCnt); triggerAfterHostChildRemoved(child); return true; } /** Returns whether this component can have a child. *

Default: return true (means it can have children). */ protected boolean isChildable() { return true; } /** Returns a live list of children. * By live we mean the developer could add or remove a child by manipulating the returned list directly. *

Default: instantiates and returns an instance of {@link Children}. */ public List getChildren() { return (List) new Children(); } /** Returns the root of the specified component. */ public Component getRoot() { for (Component comp = this;;) { final Component parent = comp.getParent(); if (parent == null) return comp; comp = parent; } } public boolean isVisible() { return _auxinf == null || _auxinf.visible; } public boolean setVisible(boolean visible) { final boolean old = _auxinf == null || _auxinf.visible; if (old != visible) { initAuxInfo().visible = visible; smartUpdate("visible", _auxinf.visible); } return old; } /** Changes the visibility directly without sending any update to the client. * It is the caller's responsibility to maintain the consistency. * It is rarely called. In most cases, you shall use {@link #setVisible} instead. * @since 5.0.4 */ protected void setVisibleDirectly(boolean visible) { initAuxInfo().visible = visible; } public String getStubonly() { final int v = _auxinf != null ? _auxinf.stubonly : 0; return v == 0 ? "inherit" : v < 0 ? "false" : "true"; } public void setStubonly(String stubonly) { int v; if ("false".equals(stubonly)) v = -1; else if ("true".equals(stubonly)) v = 1; else if ("inherit".equals(stubonly)) v = 0; else throw new UiException("Not allowed: " + stubonly); if ((_auxinf != null ? _auxinf.stubonly : 0) != v) initAuxInfo().stubonly = (byte) v; //no need to update client (it is all about server-side handling) } public void setStubonly(boolean stubonly) { setStubonly(stubonly ? "true" : "false"); } public boolean isInvalidated() { return _page == null || getAttachedUiEngine().isInvalidated(this); } public void invalidate() { if (_page != null) { getAttachedUiEngine().addInvalidate(this); } } public void invalidatePartial() { if (_page != null) { getAttachedUiEngine().addInvalidate(this, "partial"); } } /** Causes a response to be sent to the client. * It is the same as response(response.getOverrideKey(), response) * *

If {@link AuResponse#getDepends} is not null, the response * depends on the existence of the component returned by * {@link AuResponse#getDepends}. * In other words, the response is removed if the component is removed. * If it is null, the response is component-independent and it is * always sent to the client. * *

Unlike {@link #smartUpdate}, responses are sent even if * {@link Component#invalidate()} was called. * Typical examples include setting the focus, selecting the text and so on. * *

It can be called only in the request-processing and event-processing * phases; excluding the redrawing phase. * * @since 5.0.2 * @see #response(String, AuResponse) */ protected void response(AuResponse response) { response(response.getOverrideKey(), response); } /** Causes a response to be sent to the client by overriding the key * returned by {@link AuResponse#getOverrideKey}). * *

If {@link AuResponse#getDepends} is not null, the response * depends on the existence of the component returned by * {@link AuResponse#getDepends}. * In other words, the response is removed if the component is removed. * If it is null, the response is component-independent and it is * always sent to the client. * *

Unlike {@link #smartUpdate}, responses are sent even if * {@link Component#invalidate()} was called. * Typical examples include setting the focus, selecting the text and so on. * *

It can be called only in the request-processing and event-processing * phases; excluding the redrawing phase. * * @param key could be anything. * The second invocation of this method * in the same execution with the same key and the same depends * ({@link AuResponse#getDepends}) will override the previous one. * However, if key is null, it won't override any other. All responses * with key == null will be sent.
* Notice that if {@link AuResponse#getDepends} is null, then be careful * of the key you used since it is shared in the same execution * (rather than a particular component). * @since 5.0.0 (become protected) */ protected void response(String key, AuResponse response) { response(key, response, 0); } /** Causes a response to be sent to the client by overriding the key * returned by {@link AuResponse#getOverrideKey}). * *

If {@link AuResponse#getDepends} is not null, the response * depends on the existence of the component returned by * {@link AuResponse#getDepends}. * In other words, the response is removed if the component is removed. * If it is null, the response is component-independent and it is * always sent to the client. * *

Unlike {@link #smartUpdate}, responses are sent even if * {@link Component#invalidate()} was called. * Typical examples include setting the focus, selecting the text and so on. * *

It can be called only in the request-processing and event-processing * phases; excluding the redrawing phase. * * @param key could be anything. * The second invocation of this method * in the same execution with the same key and the same depends * ({@link AuResponse#getDepends}) will override the previous one. * However, if key is null, it won't override any other. All responses * with key == null will be sent.
* Notice that if {@link AuResponse#getDepends} is null, then be careful * of the key you used since it is shared in the same execution * (rather than a particular component). * @param priority The higher priority, the earlier the update is executed. * The priority of {@link #response(AuResponse)} * and {@link #response(String, AuResponse)} is assumed to be 0. *

If the priority is the same, the update is executed in the order * of first-in-first out. * @since 6.0.1 */ protected void response(String key, AuResponse response, int priority) { //if response not depend on this component, it must be generated if (_page != null) { getAttachedUiEngine().addResponse(key, response, priority); } else if (response.getDepends() != this) { final UiEngine uieng = getCurrentUiEngine(); if (uieng != null) uieng.addResponse(key, response); } } /** Smart-updates a property of the peer widget associated with * the component, running at the client, with the given value. * *

The second invocation with the same property will replace the previous * call. In other words, the same property will be set only once in * each execution. If you prefer to send both updates to the client, * use {@link #smartUpdate(String, Object, boolean)} instead. * *

This method has no effect if {@link #invalidate()} is ever invoked * (in the same execution), since {@link #invalidate()} assumes * the whole content shall be redrawn and all smart updates to * this components can be ignored, * *

Once {@link #invalidate} is called, all invocations to {@link #smartUpdate(String, Object)} * will then be ignored, and {@link #redraw} will be invoked later. * *

It can be called only in the request-processing and event-processing * phases; excluding the redrawing phase. * *

There are two ways to draw a component, one is to invoke * {@link Component#invalidate()}, and the other is {@link #smartUpdate(String, Object)}. * While {@link Component#invalidate()} causes the whole content to redraw, * {@link #smartUpdate(String, Object)} let component developer control which part * to redraw. * * @param value the new value. * If it is {@link org.zkoss.zk.au.DeferredValue}, the value * will be retrieved (by calling {@link org.zkoss.zk.au.DeferredValue#getValue}) * in the rendering phase. It is useful if the value can not be determined now. *

For some old application servers (example, Websphere 5.1), * {@link Execution#encodeURL} cannot be called in the event processing * thread. So, the developers have to use {@link org.zkoss.zk.au.DeferredValue} * or disable the use of the event processing thread * (by use of disable-event-thread in zk.xml). *

If you want to generate the JavaScript code directly (i.e., * the value is a valid JavaScript snippet), you can use * {@link JavaScriptValue}. Notice that the JavaScript code will be evaluated * before assigning it to the widget. *

If the value is a Date object, a special pattern will be generated * (a.k.a., marshaling) * to ensure it can be unmarshalled back correctly at the client. * Notice that it is marshalled to a string based * on {@link org.zkoss.util.TimeZones#getCurrent}, and then * unmarshalled back at the client. In other words, if the client * is in different time-zone, the value returned by getTime() might * be different. However, the value will remain the same if * the client marshalled the Date object back. * In other words, it assumes the browser's time zone from enduser's * perspective (not really browser's setting) shall be the same * as {@link org.zkoss.util.TimeZones#getCurrent}. *

If the value is a component, a special pattern will be generated * to ensure it can be unmarshalled back correctly at the client. *

In addition, the value can be any kind of objects that * the client accepts (marshaled by JSON) (see also {@link org.zkoss.json.JSONAware}). * @since 5.0.0 (become protected) * @see #updateByClient * @see #smartUpdate(String, Object, boolean) */ protected void smartUpdate(String attr, Object value) { smartUpdate(attr, value, false); } /** Smart-updates a property of the peer widget with the given value * that allows caller to decide whether to append or overwrite. * In other words, {@link #smartUpdate(String, Object)} is a shortcut of * smartUpdate(attr, value, false). * *

For example, if you invoke smartUpdate("attr", "value1") * and smartUpdate("attr", "value2"), then only value2 * will be sent to the client. *

However, if you invoke smartUpdate("attr", "value1", true) * and smartUpdate("attr", "value2", true), * then both value1 and value2 * will be sent to the client. In other words, wgt.setAttr("value1") * and wgt.setAttr("value2") will be invoked at the client * accordingly. * * @param append whether to append the updates of properties with the same * name. If false, only the last value of the same property will be sent * to the client. * @since 5.0.0 * @see #smartUpdate(String, Object) */ protected void smartUpdate(String attr, Object value, boolean append) { if (_page != null) getAttachedUiEngine().addSmartUpdate(this, attr, value, append); } /** A special smart update to update a value in int. *

It is the same as {@link #smartUpdate(String, Object)}. * @since 5.0.0 */ protected void smartUpdate(String attr, int value) { smartUpdate(attr, new Integer(value)); } /** A special smart update to update a value in long. *

It is the same as {@link #smartUpdate(String, Object)}. * @since 5.0.0 */ protected void smartUpdate(String attr, long value) { smartUpdate(attr, new Long(value)); } /** A special smart update to update a value in byte. *

It is the same as {@link #smartUpdate(String, Object)}. * @since 5.0.0 */ protected void smartUpdate(String attr, byte value) { smartUpdate(attr, new Byte(value)); } /** A special smart update to update a value in character. *

It is the same as {@link #smartUpdate(String, Object)}. * @since 5.0.0 */ protected void smartUpdate(String attr, char value) { smartUpdate(attr, new Character(value)); } /** A special smart update to update a value in boolean. *

It is the same as {@link #smartUpdate(String, Object)}. * @since 5.0.0 */ protected void smartUpdate(String attr, boolean value) { smartUpdate(attr, Boolean.valueOf(value)); } /** A special smart update to update a value in float. *

It is the same as {@link #smartUpdate(String, Object)}. * @since 5.0.0 */ protected void smartUpdate(String attr, float value) { smartUpdate(attr, new Float(value)); } /** A special smart update to update a value in double. *

It is the same as {@link #smartUpdate(String, Object)}. * @since 5.0.0 */ protected void smartUpdate(String attr, double value) { smartUpdate(attr, new Double(value)); } /** A special smart update to update an event listener for the * peer widget. * By default, it assumes the peer widget has a method called * setListener and it will be invoked as follows. * *

wgt.setListener([evtnm, script]);
* *

Devices that supports it in another way have to override this * method. Devices that don't support it have to override this method * to throw UnsupportedOperationException. * * @param evtnm the event name, such as onClick * @param script the script. If null, it means to remove the event * listener from the peer widget * @since 5.0.0 */ protected void smartUpdateWidgetListener(String evtnm, String script) { smartUpdate("listener", new String[] { evtnm, script }, true); } /** A special smart update to update a method or a field of the peer widget. * By default, it invokes the client widget's setOverride as follows. * *

wgt.setOverride([name: script]);
* *

Devices that supports it in another way have to override this * method. Devices that don't support it have to override this method * to throw UnsupportedOperationException. * * @param name the method name, such as setValue * @param script the content of the method or field to override. * Notice that it must be a valid JavaScript snippet. * If null, the previous method/field override * will be removed. And, the method/field defined in original widget will * be restored. * @since 5.0.0 */ protected void smartUpdateWidgetOverride(String name, String script) { smartUpdate("override", new Object[] { name, new JavaScriptValue(script) }, true); } public void detach() { if (getParent() != null) setParent(null); else setPage(null); } /** Default: does nothing. * @see ComponentCtrl#beforeChildAdded * @since 3.6.2 */ public void beforeChildAdded(Component child, Component insertBefore) { } /** Default: does nothing. * @see ComponentCtrl#beforeChildRemoved * @since 3.6.2 */ public void beforeChildRemoved(Component child) { } /** Default: If parent is null, execute the @Destroy method if any. * @see ComponentCtrl#beforeParentChanged * @since 3.6.2 */ public void beforeParentChanged(Component parent) { //ZK-1148, add a @destroy annotation method. if (parent == null)//detach WebApps.getCurrent().getConfiguration().invokeCallback("destroy", this); } /** Default: handles special event listeners. * @see ComponentCtrl#onChildAdded */ public void onChildAdded(Component child) { processCallback(AFTER_CHILD_ADDED); } /** Default: handles special event listeners. * @see ComponentCtrl#onChildRemoved */ public void onChildRemoved(Component child) { processCallback(AFTER_CHILD_REMOVED); } /** Default: handles special event listeners. * @see ComponentCtrl#onPageAttached * @since 3.0.0 */ public void onPageAttached(Page newpage, Page oldpage) { if (oldpage == null) //new added onListenerChange(newpage.getDesktop(), true); processCallback(AFTER_PAGE_ATTACHED); } /** Default: handles special event listeners. * @see ComponentCtrl#onPageDetached * @since 3.0.0 */ public void onPageDetached(Page page) { onListenerChange(page.getDesktop(), false); processCallback(AFTER_PAGE_DETACHED); } private void processCallback(String name) { Collection callbacks = getCallback(name); for (Callback callback : new ArrayList(callbacks)) { callback.call(this); removeCallback(name, callback); } } /** Returns the widget class (a.k.a., widget type), or null if not defined. *

Default: return the widget class based on the current mold * (by use of {@link ComponentDefinition#getWidgetClass}), or null * if not found. *

To override in Java, you could invoke {@link #setWidgetClass}. * To override in ZUML, you could use the client namespace as follows. *


	<window xmlns:w="http://www.zkoss.org/2005/zk/client"
	w:use="foo.MyWindow">
	</window>
	 *
* @since 5.0.0 */ public String getWidgetClass() { if (_auxinf != null && _auxinf.wgtcls != null) return _auxinf.wgtcls; final String widgetClass = _def.getWidgetClass(this, getMold()); return widgetClass != null ? widgetClass : _def.getDefaultWidgetClass(this); } public void setWidgetClass(String wgtcls) { if (wgtcls != null && wgtcls.length() > 0) { initAuxInfo().wgtcls = wgtcls; } else if (_auxinf != null) { _auxinf.wgtcls = null; } } public String getMold() { final String mold = _auxinf != null ? _auxinf.mold : null; return mold != null ? mold : DEFAULT; } public void setMold(String mold) { if (mold != null && (DEFAULT.equals(mold) || mold.length() == 0)) mold = null; if (!Objects.equals(_auxinf != null ? _auxinf.mold : DEFAULT, mold)) { if (!_def.hasMold(mold != null ? mold : DEFAULT)) throw new UiException("Unknown mold: " + mold + "; allowed: " + _def.getMoldNames()); final String oldtype = getWidgetClass(); initAuxInfo().mold = mold; if (Objects.equals(oldtype, getWidgetClass())) smartUpdate("mold", getMold()); else invalidate(); } } protected String getSpecialRendererOutput(Component comp) throws IOException { if (_page != null) { final JsContentRenderer renderer = new JsContentRenderer(); PropertiesRenderer[] prs = _page.getDesktop().getWebApp().getConfiguration().getPropertiesRenderers(); for (int j = 0; j < prs.length; j++) prs[j].renderProperties(comp, renderer); return renderer.getBuffer().toString(); } return ""; } public boolean disableClientUpdate(boolean disable) { final UiEngine uieng = _page != null ? getAttachedUiEngine() : getCurrentUiEngine(); return uieng != null && uieng.disableClientUpdate(this, disable); } public boolean addRedrawCallback(Callback callback) { return addCallback("redraw", callback); } public boolean removeRedrawCallback(Callback callback) { return removeCallback("redraw", callback); } public Collection> getRedrawCallback() { return cast(getCallback("redraw")); } public boolean addCallback(String name, Callback callback) { if (callback == null) throw new IllegalArgumentException(); if (initAuxInfo().callbacks == null) _auxinf.callbacks = new LinkedHashMap>>(2); List> list = _auxinf.callbacks.get(name); if (list == null) { list = new LinkedList>(); _auxinf.callbacks.put(name, list); } return list.add(callback); } public boolean removeCallback(String name, Callback callback) { if (initAuxInfo().callbacks != null) { List> list = _auxinf.callbacks.get(name); if (list != null) return list.remove(callback); } return false; } public Collection getCallback(String name) { if (initAuxInfo().callbacks != null) { List> list = _auxinf.callbacks.get(name); if (list != null) return cast(list); } return Collections.emptyList(); } //-- in the redrawing phase --// /** Redraws this component and all its descendants. *

Default: It uses {@link JsContentRenderer} to render all information * in JavaScript codes. For devices that don't support JavaScript, * it must override this method. *

To generate all information, it first invokes * {@link #renderProperties} to render component's * properties, * and then {@link #redrawChildren} to redraw children (and descendants) * (by calling their {@link #redraw}). * *

If a derived class wants to render more properties, it can override * {@link #renderProperties}. *

If a derived class renders only a subset of its children * (such as paging/cropping), it could override {@link #redrawChildren}. *

If a deriving class wants to do something before * {@link #renderProperties}, it has to override {@link #redraw}. *

If a deriving class doesn't want to render in JavaScript codes, * it has to override {@link #redraw} with the proper implementation * of {@link ContentRenderer}. */ public void redraw(final Writer out) throws IOException { final int order = ComponentRedraws.beforeRedraw(false); final boolean aupg = isAsyncUpdate(); final String extra; try { if (order < 0) { if (aupg) out.write('['); else { out.write(HtmlPageRenders.outSpecialJS(getDesktop())); out.write("zkx("); } } else if (order > 0) //not first child out.write(','); final JsContentRenderer renderer = new JsContentRenderer(); renderProperties(renderer); if (_page != null) { PropertiesRenderer[] prs = _page.getDesktop().getWebApp().getConfiguration().getPropertiesRenderers(); for (int j = 0; j < prs.length; j++) prs[j].renderProperties(this, renderer); } // support a way to callback for shadow element JSCumulativeContentRenderer serenderer = null; Collection> redrawCallback = getRedrawCallback(); if (!redrawCallback.isEmpty()) { serenderer = new JSCumulativeContentRenderer(); for (Callback callback : redrawCallback) { callback.call(serenderer); } } final String wgtcls = getWidgetClass(); if (wgtcls == null) throw new UiException("Widget class required for " + this + " with " + getMold()); out.write("\n['"); out.write(wgtcls); out.write("','"); out.write(getUuid()); out.write("',{"); out.write(renderer.getBuffer().toString()); out.write("},{"); out.write(serenderer == null ? "" : serenderer.toString()); out.write("},["); redrawChildren(out); out.write(']'); final String mold = getMold(); if (!DEFAULT.equals(mold)) { out.write(",'"); out.write(mold); out.write('\''); } out.write(']'); } finally { extra = ComponentRedraws.afterRedraw(); } if (order < 0) { if (aupg) { if (extra.length() > 0) { out.write(",0,null,'"); out.write(Strings.escape(extra, Strings.ESCAPE_JAVASCRIPT)); out.write('\''); } out.write(']'); } else { if (extra.length() > 0) out.write(",1"); //Bug 2983792 (delay until non-defer script evaluated) out.write(");\n"); out.write(extra); } } } private final boolean isAsyncUpdate() { final Execution exec = Executions.getCurrent(); return exec != null && exec.isAsyncUpdate(_page); } /** Redraws children (and then recursively descendants). *

Default: it invokes {@link #redraw} for all its children. *

If a derived class renders only a subset of its children * (such as paging/cropping), it could override {@link #redrawChildren}. * @since 5.0.0 * @see #redraw */ protected void redrawChildren(Writer out) throws IOException { final Object xc = getExtraCtrl(); if (xc instanceof Cropper) { final Set crop = ((Cropper) xc).getAvailableAtClient(); if (crop != null) { for (Component c : crop) if (c.getParent() == this) ((ComponentCtrl) c).redraw(out); //Note: getAvialableAtClient might return all level //of children in the same crop scope return; } } for (Component child = getFirstChild(); child != null;) { Component next = child.getNextSibling(); ((ComponentCtrl) child).redraw(out); child = next; } } /** Called by ({@link ComponentCtrl#redraw}) to render the * properties, excluding the enclosing tag and children. * *

Default: it renders {@link #getId} if it was assigned, * and event names if listened (and listed in {@link #getClientEvents}). * *

Note: it doesn't render {@link #getWidgetClass}, {@link #getUuid} * and {@link #getMold}, which are caller's job. * * @since 5.0.0 */ protected void renderProperties(ContentRenderer renderer) throws IOException { render(renderer, "id", _id); if (_auxinf != null && !_auxinf.visible) //don't call isVisible since it might be overriden (backward compatible) renderer.render("visible", false); render(renderer, "autag", getAutag()); final Desktop desktop = getDesktop(); if (this instanceof IdSpace && this.getAttribute("z$is") == null) // Used by Window and others to minimize number of bytes renderer.render("z$is", true); Boolean shallHandleImportant = null; for (Map.Entry me : getClientEvents().entrySet()) { final String evtnm = me.getKey(); final int flags = me.getValue().intValue(); boolean isImportant = false; boolean isListened = false; if ((flags & CE_IMPORTANT) != 0) { if (shallHandleImportant == null) shallHandleImportant = Boolean.valueOf(Utils.markClientInfoPerDesktop(desktop, getWidgetClass())); if (shallHandleImportant.booleanValue()) renderer.render("$$" + evtnm, (flags & CE_NON_DEFERRABLE) != 0); isImportant = true; } if (Events.isListened(this, evtnm, false)) { renderer.render('$' + evtnm, Events.isListened(this, evtnm, true)); //$onClick and so on isListened = true; } //only render the following two types when event flag is important or listened if ((flags & CE_DUPLICATE_IGNORE) != 0 && (isImportant || isListened)) { renderer.render("$$0" + evtnm, true); } if ((flags & CE_REPEAT_IGNORE) != 0 && (isImportant || isListened)) { renderer.render("$$1" + evtnm, true); } } if (_auxinf != null) _auxinf.render(renderer); // feature ZK-2822 Object o = getAttribute("$composer"); if (o != null) renderer.render("$ZKAUS$", Boolean.TRUE); o = getAttribute(Attributes.CLIENT_ROD); if (o != null) renderer.render("z$rod", (o instanceof Boolean && ((Boolean) o).booleanValue()) || !"false".equals(o)); } /** An utility to be called by {@link #renderProperties} to * render a string-value property. * It ignores if value is null or empty. * If you want to render it even if null/empty, invoke * {@link ContentRenderer#render(String, String)} directly. * @since 5.0.0 */ protected void render(ContentRenderer renderer, String name, String value) throws IOException { if (value != null && value.length() > 0) renderer.render(name, value); } /** An utility to be called by {@link #renderProperties} to * render a string-value property. * It ignores if value is null. * If you want to render it even if null, invoke * {@link ContentRenderer#render(String, Object)} directly. * @since 5.0.0 */ protected void render(ContentRenderer renderer, String name, Object value) throws IOException { if (value instanceof String) render(renderer, name, (String) value); else if (value != null) renderer.render(name, value); } /** An utility to be called by {@link #renderProperties} to * render a boolean-value property if it is true. * If you want to render it no matter true or false, use * {@link ContentRenderer#render(String, boolean)} directly. * @since 5.0.0 */ protected void render(ContentRenderer renderer, String name, boolean value) throws IOException { if (value) renderer.render(name, true); } /** Returns a map of event information that the client might send to this component. * The key of the returned map is a String instance representing the event name, * and the value an integer representing the flags * (a combination of {@link #CE_IMPORTANT}, {@link #CE_NON_DEFERRABLE}, {@link #CE_BUSY_IGNORE}, * {@link #CE_DUPLICATE_IGNORE} and {@link #CE_REPEAT_IGNORE}). *

Default: return the collection of events * added by {@link #getClientEvents}. * *

Rather than overriding this method, it is suggested * to invoke {@link #addClientEvent} in the static statement. * For example, *

public MyComponent extend HtmlBasedComponent {
	 *  static {
	 *    addClientEvent(MyComponent.class, "onOpen", 0);
	 *  }
* * @since 5.0.0 */ public Map getClientEvents() { for (Class cls = getClass(); cls != null; cls = cls.getSuperclass()) { Map events = _clientEvents.get(cls); if (events != null) return events; } return Collections.emptyMap(); } /** Adds an event that the client might send to the server. * {@link #addClientEvent} is usually called in the static clause * when the class is loaded. For example, *
public class MyWidget extends HtmlBasedComponent {
	 *  static {
	 *    addClientEvent(MyWidget.class, "onFly", 0);
	 *  }
	 *...
* *

For a programming language not easy to have the static * clause (such as Scala), {@link #addClientEvent} can be called in * the constructors. Notice that it is better not to add the client event * later than the constructor, since the derived classes will copy * the client events defined in the base class, when the first time * {@link #addClientEvent} is called with the class. * *

Version History

*

Since 5.0.4, it can be called in constructors * (in additions to the static clause). On other hand, it can only * be called in the static clause (executed when the class is loaded) * in the prior version. * @param cls the component's class (implementation class). * @param flags a combination of {@link #CE_IMPORTANT}, {@link #CE_NON_DEFERRABLE} * {@link #CE_BUSY_IGNORE}, {@link #CE_DUPLICATE_IGNORE} * and {@link #CE_REPEAT_IGNORE}. * @since 5.0.0 */ protected static void addClientEvent(Class cls, String evtnm, int flags) { Map events = _clientEvents.get(cls); if (events == null) { synchronized (cls) { events = _clientEvents.get(cls); if (events == null) { //for better performance, we pack all event names of super //classes, though it costs more memory events = new ConcurrentHashMap(8); for (Class c = cls; c != null; c = c.getSuperclass()) { final Map evts = _clientEvents.get(c); if (evts != null) { events.putAll(evts); break; } } _clientEvents.put(cls, events); } } } events.put(evtnm, new Integer(flags)); } //Event// @SuppressWarnings("deprecation") public boolean addEventListener(String evtnm, EventListener listener) { return addEventListener(listener instanceof org.zkoss.zk.ui.event.Express ? 1000 : 0, evtnm, listener); } public boolean addEventListener(int priority, String evtnm, EventListener listener) { if (evtnm == null || listener == null) throw new IllegalArgumentException("null"); if (!Events.isValid(evtnm)) throw new IllegalArgumentException("Invalid event name: " + evtnm); final boolean oldasap = Events.isListened(this, evtnm, true); if (initAuxInfo().listeners == null) _auxinf.listeners = new HashMap>(8); boolean found = false; List lis = _auxinf.listeners.get(evtnm); final EventListenerInfo listenerInfo = new EventListenerInfo(priority, listener); if (lis != null) { if (Impls.duplicateListenerIgnored()) { for (Iterator it = lis.iterator(); it.hasNext();) { final EventListenerInfo li = it.next(); if (li.listener.equals(listener)) { if (li.priority == priority) return false; //nothing to do it.remove(); //re-added later found = true; break; } } } for (ListIterator it = lis.listIterator(lis.size());;) { final EventListenerInfo li = it.hasPrevious() ? it.previous() : null; if (li == null || li.priority >= priority) { if (li != null) it.next(); it.add(listenerInfo); break; } } } else { _auxinf.listeners.put(evtnm, lis = new LinkedList()); lis.add(listenerInfo); } final Desktop desktop; if (!found && (desktop = getDesktop()) != null) { if (Events.ON_CLIENT_INFO.equals(evtnm)) { desktop.setAttribute("org.zkoss.desktop.clientinfo.enabled", true); response(new AuClientInfo(desktop)); } else if (Events.ON_PIGGYBACK.equals(evtnm)) { ((DesktopCtrl) desktop).onPiggybackListened(this, true); } else if (Events.ON_VISIBILITY_CHANGE.equals(evtnm)) { desktop.setAttribute("org.zkoss.desktop.visibilitychange.enabled", true); } else if (getClientEvents().containsKey(evtnm)) { final boolean asap = Events.isListened(this, evtnm, true); if (lis.size() == 1 || oldasap != asap) smartUpdate("$" + evtnm, asap); } } return !found; } public boolean removeEventListener(String evtnm, EventListener listener) { if (evtnm == null || listener == null) throw new IllegalArgumentException("null"); if (_auxinf != null && _auxinf.listeners != null) { final boolean oldasap = Events.isListened(this, evtnm, true); final List lis = _auxinf.listeners.get(evtnm); if (lis != null) { for (Iterator it = lis.iterator(); it.hasNext();) { final EventListenerInfo li = it.next(); if (li.listener.equals(listener)) { it.remove(); if (lis.isEmpty()) _auxinf.listeners.remove(evtnm); final Desktop desktop = getDesktop(); if (desktop != null) { onListenerChange(desktop, false); if (getClientEvents().containsKey(evtnm)) { if (lis.isEmpty() && !Events.isListened(this, evtnm, false)) smartUpdate("$" + evtnm, (Object) null); //no listener at all else if (oldasap != Events.isListened(this, evtnm, true)) smartUpdate("$" + evtnm, !oldasap); } } return true; } } } } return false; } public boolean addForward(String orgEvent, Component target, String targetEvent) { return addForward0(orgEvent, target, targetEvent, null); } public boolean addForward(String orgEvent, String targetPath, String targetEvent) { return addForward0(orgEvent, targetPath, targetEvent, null); } public boolean addForward(String orgEvent, Component target, String targetEvent, Object eventData) { return addForward0(orgEvent, target, targetEvent, eventData); } public boolean addForward(String orgEvent, String targetPath, String targetEvent, Object eventData) { return addForward0(orgEvent, targetPath, targetEvent, eventData); } /** * @param target the target. It is either a component, or a string, * which is used internal for implementing {@link #writeObject} */ private boolean addForward0(String orgEvent, Object target, String targetEvent, Object eventData) { if (orgEvent == null) orgEvent = "onClick"; else if (!Events.isValid(orgEvent)) throw new IllegalArgumentException("Illegal event name: " + orgEvent); if (targetEvent == null) targetEvent = orgEvent; else if (!Events.isValid(targetEvent)) throw new IllegalArgumentException("Illegal event name: " + targetEvent); if (initAuxInfo().forwards == null) _auxinf.forwards = new HashMap(4); ForwardInfo info = _auxinf.forwards.get(orgEvent); final List tis; if (info != null) { tis = info.targets; for (TargetInfo ti : tis) { if (Objects.equals(ti.target, target) && Objects.equals(ti.event, targetEvent)) { //found if (Objects.equals(ti.data, eventData)) { return false; } else { ti.data = eventData; return true; } } } } else { final ForwardListener listener = new ForwardListener(orgEvent); addEventListener(orgEvent, listener); info = new ForwardInfo(listener, tis = new LinkedList()); _auxinf.forwards.put(orgEvent, info); } tis.add(new TargetInfo(target, targetEvent, eventData)); return true; } public boolean removeForward(String orgEvent, Component target, String targetEvent) { return removeForward0(orgEvent, target, targetEvent); } public boolean removeForward(String orgEvent, String targetPath, String targetEvent) { return removeForward0(orgEvent, targetPath, targetEvent); } private boolean removeForward0(String orgEvent, Object target, String targetEvent) { if (_auxinf != null && _auxinf.forwards != null) { final ForwardInfo info = _auxinf.forwards.get(orgEvent); if (info != null) { final List tis = info.targets; for (Iterator it = tis.iterator(); it.hasNext();) { final TargetInfo ti = it.next(); if (Objects.equals(ti.event, targetEvent) && Objects.equals(ti.target, target)) { //found it.remove(); //remove it if (tis.isEmpty()) { //no more event _auxinf.forwards.remove(orgEvent); removeEventListener(orgEvent, info.listener); } return true; } } } } return false; } public boolean isListenerAvailable(String evtnm, boolean asap) { if (_auxinf != null && _auxinf.listeners != null) { final List lis = _auxinf.listeners.get(evtnm); if (lis != null) { if (!asap) return !lis.isEmpty(); for (EventListenerInfo li : lis) { if (!(li.listener instanceof Deferrable) || !(((Deferrable) li.listener).isDeferrable())) return true; } } } return false; } /** @deprecated As of release 6.0, replaced with {@link #getEventListeners}. */ public Iterator> getListenerIterator(String evtnm) { if (_auxinf != null && _auxinf.listeners != null) { final List lis = _auxinf.listeners.get(evtnm); if (lis != null) return CollectionsX.comodifiableIterator(lis, _listenerInfoConverter); } return CollectionsX.emptyIterator(); } public Iterable> getEventListeners(String evtnm) { if (_auxinf != null && _auxinf.listeners != null) { final List lis = _auxinf.listeners.get(evtnm); if (lis != null) return new Iterable>() { public Iterator> iterator() { return CollectionsX.comodifiableIterator(lis, _listenerInfoConverter); } }; } return CollectionsX.emptyIterable(); } public void applyProperties() { _def.applyProperties(this); } public ComponentDefinition getDefinition() { return _def; } //-- ComponentCtrl --// public void setDefinition(ComponentDefinition compdef) { if (compdef == null) throw new IllegalArgumentException("null"); if (!compdef.isInstance(this)) throw new IllegalArgumentException("Incompatible " + compdef + " for " + this); _def = compdef; } public void setDefinition(String name) { final Execution exec = Executions.getCurrent(); if (exec != null) { final ExecutionCtrl execCtrl = (ExecutionCtrl) exec; final PageDefinition pgdef = execCtrl.getCurrentPageDefinition(); final Page page = execCtrl.getCurrentPage(); ComponentDefinition compdef = pgdef != null ? pgdef.getComponentDefinition(name, true) : page != null ? page.getComponentDefinition(name, true) : null; if (compdef == null) compdef = Impls.getDefinitionByDeviceType(this, exec.getDesktop().getDeviceType(), name); if (compdef != null) { setDefinition(compdef); return; } } else { for (String deviceType : LanguageDefinition.getDeviceTypes()) { final ComponentDefinition compdef = Impls.getDefinitionByDeviceType(this, deviceType, name); if (compdef != null) { setDefinition(compdef); return; } } } throw new ComponentNotFoundException(name + " not found"); } public ZScript getEventHandler(String evtnm) { final EventHandler evthd = _auxinf != null && _auxinf.evthds != null ? _auxinf.evthds.get(this, evtnm) : null; return evthd != null ? evthd.getZScript() : null; } public void addSharedEventHandlerMap(EventHandlerMap evthds) { if (evthds != null && !evthds.isEmpty()) { unshareEventHandlerMap(false); if (initAuxInfo().evthds == null) { _auxinf.evthds = evthds; _auxinf.evthdsShared = true; } else { _auxinf.evthds.addAll(evthds); } final Desktop desktop = getDesktop(); if (desktop != null) onListenerChange(desktop, true); } } public Set getEventHandlerNames() { if (_auxinf != null && _auxinf.evthds != null) return _auxinf.evthds.getEventNames(); return Collections.emptySet(); } private void onListenerChange(Desktop desktop, boolean listen) { if (listen) { if (Events.isListened(this, Events.ON_CLIENT_INFO, false)) { //asap+deferrable response(new AuClientInfo(desktop)); getDesktop().setAttribute("org.zkoss.desktop.clientinfo.enabled", true); //We always fire event not a root, since we don't like to //check when setParent or setPage is called } if (Events.isListened(this, Events.ON_PIGGYBACK, false)) ((DesktopCtrl) desktop).onPiggybackListened(this, true); if (Events.isListened(this, Events.ON_VISIBILITY_CHANGE, false)) getDesktop().setAttribute("org.zkoss.desktop.visibilitychange.enabled", true); } else { if (!Events.isListened(this, Events.ON_PIGGYBACK, false)) ((DesktopCtrl) desktop).onPiggybackListened(this, false); } } public void addEventHandler(String name, EventHandler evthd) { if (name == null || evthd == null) throw new IllegalArgumentException("name and evthd required"); unshareEventHandlerMap(true); _auxinf.evthds.add(name, evthd); } /** Clones the shared event handlers, if shared. * @param autocreate whether to create an event handler map if not available. */ private void unshareEventHandlerMap(boolean autocreate) { if (_auxinf != null && _auxinf.evthdsShared) { _auxinf.evthds = (EventHandlerMap) _auxinf.evthds.clone(); _auxinf.evthdsShared = false; } else if (autocreate && initAuxInfo().evthds == null) { _auxinf.evthds = new EventHandlerMap(); } } /** @deprecated As of release 6.0.0, replaced with * {@link #getAnnotation(String, String)}. */ public Annotation getAnnotation(String annotName) { return getAnnotation(null, annotName); } public Annotation getAnnotation(String propName, String annotName) { return _auxinf != null && _auxinf.annots != null ? _auxinf.annots.getAnnotation(propName, annotName) : null; } public Collection getAnnotations(String propName, String annotName) { if (_auxinf != null && _auxinf.annots != null) return _auxinf.annots.getAnnotations(propName, annotName); return Collections.emptyList(); } /** @deprecated As of release 6.0.0, replaced with {@link #getAnnotations(String)}. */ public Collection getAnnotations() { return getAnnotations(null); } public Collection getAnnotations(String propName) { if (_auxinf != null && _auxinf.annots != null) return _auxinf.annots.getAnnotations(propName); return Collections.emptyList(); } public List getAnnotatedPropertiesBy(String annotName) { if (_auxinf != null && _auxinf.annots != null) return _auxinf.annots.getAnnotatedPropertiesBy(annotName); return Collections.emptyList(); } public List getAnnotatedProperties() { if (_auxinf != null && _auxinf.annots != null) return _auxinf.annots.getAnnotatedProperties(); return Collections.emptyList(); } /** Add a map of annotations which is shared by other components. * In other words, this component shall have all annotations * defined in the specified map, annots. Meanwhile, this component * shall not modify annots, since it is shared. * The caller shall not change annots after the invocation, too */ private void addSharedAnnotationMap(AnnotationMap annots) { if (annots != null && !annots.isEmpty()) { unshareAnnotationMap(false); if (initAuxInfo().annots == null) { _auxinf.annots = annots; _auxinf.annotsShared = true; } else { _auxinf.annots.addAll(annots); } } } /** @deprecated As of release 6.0.0, replaced with * {@link #addAnnotation(String, String, Map)} */ public void addAnnotation(String annotName, Map annotAttrs) { addAnnotation(null, annotName, annotAttrs); } public void addAnnotation(String propName, String annotName, Map annotAttrs) { unshareAnnotationMap(true); _auxinf.annots.addAnnotation(propName, annotName, fixAttrValues(annotAttrs), null); } /** Used to resolve the backward compatibility: * ZK 6 expects String[], but ZK 5 might pass String as the value. *

For better performance, we don't put this method to AnnotationMap. * After all, ComponentInfo/ComponentDefinition.addAnnotation not for app */ private Map fixAttrValues(Map attrs) { if (attrs == null) return null; for (Map.Entry m0 : attrs.entrySet()) { Object key = m0.getKey(); Object val = m0.getValue(); if ((key != null && !(key instanceof String)) || (val != null && !(val instanceof String[]))) { //need to convert final Map as = new LinkedHashMap(4); for (Map.Entry me : attrs.entrySet()) { key = me.getKey(); if (key != null && !(key instanceof String)) throw new UiException("Illegal attribute name, " + key); val = me.getValue(); if (val == null || val instanceof String[]) as.put((String) key, (String[]) val); else if (val instanceof String) as.put((String) key, new String[] { (String) val }); else throw new UiException("Illegagl attribute value, " + val); } return as; } } return cast(attrs); } /** Clones the shared annotations, if shared. * @param autocreate whether to create an annotation map if not available. */ private void unshareAnnotationMap(boolean autocreate) { if (_auxinf != null && _auxinf.annotsShared) { _auxinf.annots = (AnnotationMap) _auxinf.annots.clone(); _auxinf.annotsShared = false; } else if (autocreate && initAuxInfo().annots == null) { _auxinf.annots = new AnnotationMap(); } } public void sessionWillPassivate(Page page) { Set uniqueAttrs = new LinkedHashSet(); //ZK-2701: use set to record unique attrs and listeners if (_auxinf != null && _auxinf.attrs != null) { uniqueAttrs.addAll(_auxinf.attrs.getAttributes().values()); willPassivate(uniqueAttrs); //ZK-2701: only passivate the same object once willPassivate(_auxinf.attrs.getListeners()); } if (_auxinf != null && _auxinf.listeners != null) for (Iterator> it = CollectionsX .comodifiableIterator(_auxinf.listeners.values()); it.hasNext();) for (EventListenerInfo li : it.next()) { if (uniqueAttrs.add(li.listener)) //ZK-2701: only passivate the same object once willPassivate(li.listener); } for (AbstractComponent p = (AbstractComponent) getFirstChild(); p != null; p = p._next) p.sessionWillPassivate(page); //recursive } public void sessionDidActivate(Page page) { _page = page; Set uniqueAttrs = new LinkedHashSet(); //ZK-2701: use set to record unique attrs and listeners if (_auxinf != null && _auxinf.attrs != null) { uniqueAttrs.addAll(_auxinf.attrs.getAttributes().values()); didActivate(uniqueAttrs); //ZK-2701: only activate the same object once didActivate(_auxinf.attrs.getListeners()); if (_parent == null) _auxinf.attrs.notifyParentChanged(_page); } if (_auxinf != null && _auxinf.listeners != null) for (Iterator> it = CollectionsX .comodifiableIterator(_auxinf.listeners.values()); it.hasNext();) //ZK-3224: Prevent ConcurrentModificationException for (Iterator itli = CollectionsX.comodifiableIterator(it.next()); itli.hasNext();) { EventListenerInfo li = itli.next(); if (uniqueAttrs.add(li.listener)) //ZK-2701: only activate the same object once didActivate(li.listener); } for (AbstractComponent p = (AbstractComponent) getFirstChild(); p != null; p = p._next) p.sessionDidActivate(page); //recursive } /** Utility to invoke {@link ComponentActivationListener#willPassivate} * for each object in the collection. * @param c a collection of objects. Ignored if null. * @since 3.6.4 */ @SuppressWarnings("unchecked") protected void willPassivate(Collection c) { if (c != null) for (Iterator it = new ArrayList(c).iterator(); it.hasNext();) willPassivate(it.next()); } /** Utility to invoke {@link ComponentActivationListener#willPassivate} * for the specified object. * @param o the object to invoke. Ignore if * ComponentActivationListener not implemented or null. * @since 3.6.4 */ protected void willPassivate(Object o) { if (o instanceof ComponentActivationListener) ((ComponentActivationListener) o).willPassivate(this); } /** Utility to invoke {@link ComponentActivationListener#didActivate} * for each object in the collection. * @param c a collection of objects. Ignored if null. * @since 3.6.4 */ @SuppressWarnings("unchecked") protected void didActivate(Collection c) { if (c != null) for (Iterator it = new ArrayList(c).iterator(); it.hasNext();) didActivate(it.next()); } /** Utility to invoke {@link ComponentActivationListener#didActivate} * for the specified object. * @param o the object to invoke. Ignore if * ComponentActivationListener not implemented or null. * @since 3.6.4 */ protected void didActivate(Object o) { if (o instanceof ComponentActivationListener) ((ComponentActivationListener) o).didActivate(this); } /** Returns the extra controls that tell ZK how to handle this component * specially. * It is used only by component developers. * *

Default: null. * * @see ComponentCtrl#getExtraCtrl */ public Object getExtraCtrl() { return null; } private static HashMap _properties = new HashMap(3); static { _properties.put("id", new StringPropertyAccess() { public void setValue(Component cmp, String id) { cmp.setId(id); } public String getValue(Component cmp) { return cmp.getId(); } }); _properties.put("mold", new StringPropertyAccess() { public void setValue(Component cmp, String mold) { cmp.setMold(mold); } public String getValue(Component cmp) { return cmp.getMold(); } }); _properties.put("visible", new BooleanPropertyAccess() { public void setValue(Component cmp, Boolean visible) { cmp.setVisible(visible); } public Boolean getValue(Component cmp) { return cmp.isVisible(); } }); } public PropertyAccess getPropertyAccess(String prop) { return _properties.get(prop); } /** Notifies that an {@link WrongValueException} instance is thrown, * and {@link WrongValueException#getComponent} is this component. * It is a callback and the component can store the error message, * show up the custom information, or even 'eat' the exception. * *

Default: does nothing but returns ex. * * @param ex the exception being thrown (never null) * @return the exception to throw, or null to ignore the exception * In most cases, just return ex * @since 2.4.0 */ public WrongValueException onWrongValue(WrongValueException ex) { return ex; } public AuService getAuService() { return _auxinf != null ? _auxinf.ausvc : null; } public void setAuService(AuService ausvc) { if (ausvc != null) initAuxInfo().ausvc = ausvc; else if (_auxinf != null) _auxinf.ausvc = null; } /** Handles an AU request. It is invoked internally. * *

Default: it handles echo and setAttr, and it converts other request * to an event (by {@link Event#getEvent}) and then posts the event * (by {@link Events#postEvent}). * *

Application developer can plug the custom service to handle * the AU request by {@link #setAuService}. * @since 5.0.0 * @see #setAuService */ public void service(AuRequest request, boolean everError) { final String cmd = request.getCommand(); if ("echo".equals(cmd)) { final List data2 = (List) request.getData().get(""); Events.postEvent(new Event((String) data2.get(0), this, data2.size() > 1 ? AuEcho.getData(this, data2.get(1)) : null)); } else if ("setAttr".equals(cmd)) { final List data2 = (List) request.getData().get(""); updateByClient((String) data2.get(0), data2.get(1)); } else Events.postEvent(Event.getEvent(request)); } public void service(Event event, Scope scope) throws Exception { final String evtnm = event.getName(); final Method mtd = ComponentsCtrl.getEventMethod(getClass(), evtnm); if (_auxinf != null) service(event, scope, _auxinf.listeners != null ? _auxinf.listeners.get(evtnm) : null, _auxinf.evthds != null ? _auxinf.evthds.get(this, evtnm) : null, mtd, false); else service(event, scope, null, null, mtd, false); } /** * @param skipPageListener whether to skip page's event listener. */ /*package*/ void service(Event event, Scope scope, final List listeners, final EventHandler evthd, final Method mtd, boolean skipPageListener) throws Exception { final Execution exec = Executions.getCurrent(); final Desktop desktop = exec.getDesktop(); final Page page = _page != null ? _page : desktop.getFirstPage(); if (page == null || !page.isAlive()) { String msg = (page == null ? "No page is available in " + desktop : "Page " + page + " was destroyed"); if (desktop.isAlive()) msg += " (but desktop is alive)"; else msg += " because desktop was destroyed.\n" + "It is usually caused by invalidating the native session directly. " + "If it is required, please set Attributes.RENEW_NATIVE_SESSION first."; log.warn(msg); } //EventListener always is called even if comp isn't attached (e.g., EventQueue) final ExecInfo execinf; ((ExecutionCtrl) exec).setExecutionInfo(execinf = new ExecInfo(event)); if (listeners != null) for (Iterator it = new LinkedList(listeners).iterator(); it .hasNext();) { final EventListenerInfo li = it.next(); if (li.priority < 1000) break; //no more execinf.update(null, li.listener, null); onEvent(li.listener, event); if (!event.isPropagatable()) return; //done } //ZScript called only if comp attached to an active page if (page != null && getDesktop() != null) { //this.desktop, not exec.desktop final ZScript zscript = evthd != null ? evthd.getZScript() : null; if (zscript != null) { execinf.update(null, null, zscript); page.interpret(zscript.getLanguage(), zscript.getContent(page, this), scope); if (!event.isPropagatable()) return; //done } } if (listeners != null) for (Iterator it = new LinkedList(listeners).iterator(); it .hasNext();) { final EventListenerInfo li = it.next(); if (li.priority < 1000) { execinf.update(null, li.listener, null); onEvent(li.listener, event); if (!event.isPropagatable()) return; //done } } //Like EventListener, method is always called if (mtd != null) { execinf.update(mtd, null, null); if (mtd.getParameterTypes().length == 0) mtd.invoke(this, (Object[]) null); else mtd.invoke(this, new Object[] { event }); if (!event.isPropagatable()) return; //done } if (!skipPageListener && page != null) for (EventListener el : page.getEventListeners(event.getName())) { //Note: CollectionsX.comodifiableIterator is used so OK to iterate execinf.update(null, el, null); onEvent(el, event); if (!event.isPropagatable()) return; //done } } @SuppressWarnings("unchecked") private static void onEvent(EventListener listener, Event event) throws Exception { listener.onEvent(event); } public EventListenerMap getEventListenerMap() { return new EventListenerMapImpl(_auxinf != null ? _auxinf.listeners : null, _auxinf != null ? _auxinf.evthds : null); } /** Called when the widget running at the client asks the server * to update a value. The update is caused by an AU request named setAttr * (by invoking zk.Widget's smartUpdate at client). * *

By default, it does nothing but log a warning message, since * it is not safe to allow the client to update a field arbitrary. *

However, if you'd like to allow the update for a particular component * you could do one of the following *

    *
  1. For component developers: override this method to update the field * directly. For example,
    protected void updateByClient(String name, Object value) {
    	if ("disabled".equals(name))
    		setDisabled(name, ((Boolean)value).booleanValue());
    	else
    		super.updateByClient(name, value);
  2. *
  3. For application developers: set an attribute called * org.zkoss.zk.ui.updateByClient to be true. * Then, this method will use reflection to find out the setter to update * the value. Nothing happens if the method is not found.
  4. * *

    Notice: this method will invoke {@link #disableClientUpdate} to * disable any update to the client, when calling the setter. * *

    If you want to enable the client update for all instances of a given * component (though not recommended for the security reason), you could * refer to here. * *

    See also zk.Widget.smartUpdate(). * @since 5.0.0 */ protected void updateByClient(String name, Object value) { Object o = getAttribute("org.zkoss.zk.ui.updateByClient"); if (!(o instanceof Boolean && ((Boolean) o).booleanValue()) && !(o instanceof String && "true".equals(o))) { log.warn("Ignore update of " + name + "=" + value + " from client for " + this.getClass()); return; //ignored } Method m; final String mtdnm = Classes.toMethodName(name, "set"); Object[] args = new Object[] { value }; try { m = Classes.getMethodByObject(getClass(), mtdnm, args); } catch (NoSuchMethodException ex) { try { m = Classes.getCloseMethod(getClass(), mtdnm, new Class[] { String.class }); } catch (NoSuchMethodException e2) { try { m = Classes.getCloseMethod(getClass(), mtdnm, new Class[] { null }); } catch (NoSuchMethodException e3) { log.warn("setter not found", ex); return; //ignore it } } try { args[0] = Classes.coerce(m.getParameterTypes()[0], value); } catch (Throwable e2) { log.warn(m + " requires " + m.getParameterTypes()[0] + ", not " + value); return; //ignore it } } disableClientUpdate(true); try { m.invoke(this, args); } catch (Throwable ex) { throw UiException.Aide.wrap(ex); } finally { disableClientUpdate(false); } } //-- Object --// public String toString() { final String clsnm = getClass().getName(); final int j = clsnm.lastIndexOf('.'); return "<" + clsnm.substring(j + 1) + ' ' + _uuid + (Strings.isEmpty(_id) ? "" : "#" + _id) + '>'; } /** Holds info shared of the same ID space. */ private class SpaceInfo { /** A map of ((String id, Component fellow). */ private Map fellows = new HashMap(32); } private class ChildIter implements ListIterator { private AbstractComponent _p, _lastRet; private int _j; private int _modCntSnap; private ChildIter(int index) { int nChild; if (index < 0 || index > (nChild = nChild())) throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + nChild()); if (index < (nChild >> 1)) { _p = _chdinf.first; 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 : _chdinf.last; } _modCntSnap = modCntChd(); } public boolean hasNext() { return _j < nChild(); } public Component next() { if (_j >= nChild()) throw new java.util.NoSuchElementException(); checkComodification(); _lastRet = _p; _p = _p._next; _j++; return _lastRet; } public boolean hasPrevious() { return _j > 0; } public Component previous() { if (_j <= 0) throw new java.util.NoSuchElementException(); checkComodification(); _lastRet = _p = _p != null ? _p._prev : _chdinf.last; _j--; return _lastRet; } private void checkComodification() { if (modCntChd() != _modCntSnap) throw new java.util.ConcurrentModificationException(); } public int nextIndex() { return _j; } public int previousIndex() { return _j - 1; } public void add(Component newChild) { if (newChild.getParent() == AbstractComponent.this) throw new UnsupportedOperationException("Unable to add component with the same parent: " + newChild); //1. it is confusing to allow adding (with replace) //2. the code is sophisticated checkComodification(); insertBefore(newChild, _p); ++_j; _lastRet = null; //spec: cause remove to throw ex if no next/previous ++_modCntSnap; //don't assign from modCntChd directly since deriving class //might manipulate others in insertBefore } public void remove() { if (_lastRet == null) throw new IllegalStateException(); checkComodification(); if (_p == _lastRet) _p = _lastRet._next; //previous was called else --_j; //next was called removeChild(_lastRet); _lastRet = null; ++_modCntSnap; } public void set(Component o) { throw new UnsupportedOperationException(); //Possible to implement this but confusing to developers //if o has the same parent (since we have to move) } } //Cloneable// public Object clone() { final AbstractComponent clone; try { clone = (AbstractComponent) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } //1. make it not belonging to any page clone._page = null; clone._parent = null; //2. clone AuxInfo if (_auxinf != null) clone._auxinf = (AuxInfo) clone._auxinf.clone(); //3. clone children (deep cloning) if (_chdinf != null) { clone._chdinf = _chdinf.clone(clone); //child's attrs's notification for (AbstractComponent p = clone._chdinf.first; p != null; p = p._next) if (p._auxinf != null && p._auxinf.attrs != null) p._auxinf.attrs.notifyParentChanged(clone); } //4. init AuxInfo if (_auxinf != null) _auxinf.initClone(clone, clone._auxinf); return clone; } private void cloneSpaceInfoFrom(SpaceInfo from) { //rebuild ID space by binding itself and all children if (!isAutoId(_id)) this.bindToIdSpace(this); for (AbstractComponent p = (AbstractComponent) getFirstChild(); p != null; p = p._next) addToIdSpacesDown(p, (IdSpace) this); } //Serializable// //NOTE: they must be declared as private private synchronized void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { //No need to unshare annots and evthds, since stored as an independent copy s.defaultWriteObject(); //write definition if (_def == ComponentsCtrl.DUMMY) { s.writeObject(null); } else { LanguageDefinition langdef = _def.getLanguageDefinition(); if (langdef != null) { s.writeObject(langdef.getName()); s.writeObject(_def.getName()); } else { s.writeObject(_def); } } //write children for (AbstractComponent p = (AbstractComponent) getFirstChild(); p != null; p = p._next) s.writeObject(p); s.writeObject(null); //write auxinf if necessary if (_auxinf == null) return; //write attrs if (_auxinf.attrs != null) { final Map attrmap = _auxinf.attrs.getAttributes(); willSerialize(attrmap.values()); final List attrlns = _auxinf.attrs.getListeners(); willSerialize(attrlns); Serializables.smartWrite(s, attrmap); Serializables.smartWrite(s, attrlns); } else { Serializables.smartWrite(s, (Map) null); Serializables.smartWrite(s, (List) null); } EventListenerInfo.write(s, this, _auxinf.listeners); willSerialize(_auxinf.ausvc); Serializables.smartWrite(s, _auxinf.ausvc); } /** Utility to invoke {@link ComponentSerializationListener#willSerialize} * for each object in the collection. * @param c a collection of objects. Ignored if null. * @since 3.6.4 */ protected void willSerialize(Collection c) { if (c != null) for (Iterator it = c.iterator(); it.hasNext();) willSerialize(it.next()); } /** Utility to invoke {@link ComponentSerializationListener#willSerialize} * for the specified object. * @param o the object to invoke. Ignore if * ComponentSerializationListener not implemented or null. * @since 3.6.4 */ protected void willSerialize(Object o) { if (o instanceof ComponentSerializationListener) ((ComponentSerializationListener) o).willSerialize(this); } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); //read definition Object def = s.readObject(); if (def instanceof String) { LanguageDefinition langdef = null; try { langdef = LanguageDefinition.lookup((String) def); } catch (DefinitionNotFoundException ex) { // expected } if (langdef != null) { _def = langdef.getComponentDefinitionIfAny((String) s.readObject()); //don't throw exception since some might not be associated //with a definition (e.g., JSP's native and page components) } else { s.readObject(); //ignore the component name _def = null; } } else { _def = (ComponentDefinition) def; } if (_def == null) _def = ComponentsCtrl.DUMMY; //read children for (AbstractComponent q = null;;) { final AbstractComponent child = (AbstractComponent) s.readObject(); if (child == null) { if (_chdinf != null) _chdinf.last = q; break; //no more } ++initChildInfo().nChild; if (q != null) q._next = child; else _chdinf.first = child; child._prev = q; child._parent = this; q = child; } //Read auxinf if (_auxinf == null) return; //read attrs attrs(); final Map attrmap = _auxinf.attrs.getAttributes(); Serializables.smartRead(s, attrmap); final List attrlns = _auxinf.attrs.getListeners(); Serializables.smartRead(s, attrlns); if (attrmap.isEmpty() && attrlns.isEmpty()) _auxinf.attrs = null; else if (_parent != null) _auxinf.attrs.notifyParentChanged(_parent); _auxinf.listeners = EventListenerInfo.read(s, this); //restore _auxinf.spaceInfo if (this instanceof IdSpace) { _auxinf.spaceInfo = new SpaceInfo(); //restore ID space by binding itself and all children if (!isAutoId(_id)) bindToIdSpace(this); for (AbstractComponent ac = (AbstractComponent) getFirstChild(); ac != null; ac = ac._next) addToIdSpacesDown(ac, (IdSpace) this); } //didDeserialize didDeserialize(attrmap.values()); didDeserialize(attrlns); didDeserialize(_auxinf.ausvc = (AuService) s.readObject()); } /** Utility to invoke {@link ComponentSerializationListener#didDeserialize} * for each object in the collection. * @param c a collection of objects. Ignored if null. * @since 3.6.4 */ protected void didDeserialize(Collection c) { if (c != null) for (Iterator it = c.iterator(); it.hasNext();) didDeserialize(it.next()); } /** Utility to invoke {@link ComponentSerializationListener#didDeserialize} * for the specified object. * @param o the object to invoke. Ignore if * ComponentSerializationListener not implemented or null. * @since 3.6.4 */ protected void didDeserialize(Object o) { if (o instanceof ComponentSerializationListener) ((ComponentSerializationListener) o).didDeserialize(this); } /** Used to forward events (for the forward conditions). */ private class ForwardListener implements EventListener, ComponentCloneListener, java.io.Serializable { //Note: it is not serializable since it is handled by //AbstractComponent.writeObject private final String _orgEvent; private ForwardListener(String orgEvent) { _orgEvent = orgEvent; } public void onEvent(Event event) { final ForwardInfo info = _auxinf.forwards.get(_orgEvent); if (info != null) for (TargetInfo ti : new ArrayList(info.targets)) { Component target = resolveForwardTarget(ti.target); if (target == null) { final IdSpace owner = getSpaceOwner(); if (owner instanceof Component) { target = (Component) owner; } else { //Use the root component instead for (target = AbstractComponent.this;;) { final Component p = target.getParent(); if (p == null) break; target = p; } } } //bug #2790393 Forward event listener shall be called immediately //(since 3.6.2) change from postEvent to sendEvent to //make forward event deterministic Events.sendEvent(new ForwardEvent(ti.event, target, event, ti.data)); } } //ComponentCloneListener// public Object willClone(Component comp) { return null; //handle by AbstractComponent.clone } } private Component resolveForwardTarget(Object target) { return target instanceof String ? Components.pathToComponent((String) target, this) : (Component) target; } /** Returns the default mold for the given class. *

    Default: check the library property called xxx.mold, where xxx is * the name of the give class. If not found or empty, "default" is assumed. *

    Subclass might override this method to use the default mold of the base * class, such as *

    
    	 *protected String getDefaultMold(Class klass) {
    	 *   return super.getDefaultMold(Button.class);
    	 *}
    * @since 5.0.3 */ protected String getDefaultMold(Class klass) { return Impls.defaultMold(klass); } private final AuxInfo initAuxInfo() { if (_auxinf == null) _auxinf = new AuxInfo(); return _auxinf; } public Template getTemplate(String name) { return _auxinf != null && _auxinf.templates != null ? _auxinf.templates.get(name) : null; } public Template setTemplate(String name, Template template) { if (template == null) { return _auxinf != null && _auxinf.templates != null ? _auxinf.templates.remove(name) : null; } else { AuxInfo auxinf = initAuxInfo(); if (auxinf.templates == null) auxinf.templates = new HashMap(4); return auxinf.templates.put(name, template); } } public Set getTemplateNames() { if (_auxinf != null && _auxinf.templates != null) return _auxinf.templates.keySet(); return Collections.emptySet(); } public Component query(String selector) { final Iterator found = Selectors.iterable(this, selector).iterator(); return found.hasNext() ? found.next() : null; } public Iterable queryAll(String selector) { return Selectors.iterable(this, selector); } /** Merge multiple members into an single object (and create on demand) * to minimize the footprint * @since 5.0.4 */ private static class AuxInfo implements java.io.Serializable, Cloneable { /** shadow roots since 8.0.0 */ private List seRoots; /** The mold. */ private String mold; /** The info of the ID space, or null if IdSpace is NOT implemented. */ private transient SpaceInfo spaceInfo; /** Component attributes. */ private transient SimpleScope attrs; /** A map of event listener: Map(evtnm, List(EventListenerInfo)). */ private transient Map> listeners; /** A map of annotations. Serializable since a component might have * its own annotations. */ private AnnotationMap annots; /** A map of event handler to handle events. */ private EventHandlerMap evthds; /** A map of forward conditions: * Map(String orgEvt, ForwardInfo fi>). */ private Map forwards; /** the Au service. */ private transient AuService ausvc; /** The widget class. */ private String wgtcls; /** A map of client event handlers, Map(String evtnm, String script). */ private Map wgtlsns; /** A map of client properties to override, Map(String name, String script). */ private Map wgtovds; /** A map of client DOM attributes to set, Map(String name, String value). */ private Map domattrs; /** A map of component lifecycle callback to set. Since 8.0.0 */ private Map>> callbacks; /** The AU tag. */ private String autag; /** The templates. */ private Map templates; /** Whether this component is stub-only (0: inherit, -1: false, 1: true). */ private byte stubonly; /** Whether annots is shared with other components. */ private transient boolean annotsShared; /** Whether evthds is shared with other components. */ private transient boolean evthdsShared; /** Whether this component is visible. */ private boolean visible = true; //F80 - store subtree's binder annotation count private boolean hasBindingAnnot = false; private int subAnnotCnt = 0; public Object clone() { final AuxInfo clone; try { clone = (AuxInfo) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } if (wgtlsns != null) clone.wgtlsns = new LinkedHashMap(wgtlsns); if (wgtovds != null) clone.wgtovds = new LinkedHashMap(wgtovds); if (domattrs != null) clone.domattrs = new LinkedHashMap(domattrs); if (callbacks != null) clone.callbacks = new LinkedHashMap>>(callbacks); //clone annotation and event handlers if (!annotsShared && annots != null) clone.annots = (AnnotationMap) annots.clone(); if (!evthdsShared && evthds != null) clone.evthds = (EventHandlerMap) evthds.clone(); return clone; } /** Clone for the stub component ({@link #replaceWith(AbstractComponent, boolean, boolean, boolean)}). */ private AuxInfo cloneStub(AbstractComponent owner, boolean bListener) { //No need to clone visible since it is meaningless in StubComponent if (bListener && (evthds != null || listeners != null)) { //No need to make a copy of evthds and listeners since Stub replaced the old one final AuxInfo clone = new AuxInfo(); clone.evthdsShared = evthdsShared; clone.evthds = evthds; clone.listeners = listeners; return clone; } return null; } /** 2nd phase of clone (after children are cloned). */ private void initClone(AbstractComponent owner, AuxInfo clone) { //spaceinfo (after children is cloned) if (spaceInfo != null) { clone.spaceInfo = owner.new SpaceInfo(); owner.cloneSpaceInfoFrom(spaceInfo); } //clone attrs if (attrs != null) clone.attrs = attrs.clone(owner); //clone listener cloneListeners(owner, clone); //clone forwards (after children is cloned) if (forwards != null) { clone.forwards = null; for (Map.Entry me : forwards.entrySet()) { final String orgEvent = me.getKey(); final ForwardInfo info = me.getValue(); final List tis = info.targets; for (TargetInfo ti : tis) owner.addForward0(orgEvent, ti.target, ti.event, ti.data); } } //AuService if (ausvc instanceof ComponentCloneListener) clone.ausvc = (AuService) ((ComponentCloneListener) ausvc).willClone(owner); } private void cloneListeners(AbstractComponent owner, AuxInfo clone) { if (listeners != null) { clone.listeners = new HashMap>(4); for (Map.Entry> me : listeners.entrySet()) { final List list = new LinkedList(); for (EventListenerInfo li : me.getValue()) { if (li.listener instanceof ComponentCloneListener) { Object val = ((ComponentCloneListener) li.listener).willClone(owner); if (val == null) continue; //don't use it in clone li = new EventListenerInfo(li.priority, (EventListener) val); } list.add(li); } if (!list.isEmpty()) clone.listeners.put(me.getKey(), list); } } } private void render(ContentRenderer renderer) throws IOException { renderer.renderWidgetListeners(wgtlsns); renderer.renderWidgetOverrides(wgtovds); renderer.renderClientAttributes(domattrs); } } private final ChildInfo initChildInfo() { if (_chdinf == null) _chdinf = new ChildInfo(); return _chdinf; } /*package*/ static class ChildInfo implements Cloneable/* not java.io.Serializable*/ { /** The first child. */ /*package*/ AbstractComponent first; /** The last child. */ /*package*/ AbstractComponent last; /** # of children. */ /*package*/ int nChild; /** Set of components that are being added or removed. * _aring[0]: add, _aring[1]: remove * It is used to prevent dead-loop between {@link #removeChild} * and {@link #setParent}. */ private Set[] _aring; //use an array to save memory /** The modification count used to avoid co-modification of _next, _prev.. */ /*package*/ int modCntChd; /** The virtual ID space used when this component is a root component, * but not attached to a page, nor implement IdSpace. */ private IdSpace vispace; private ChildInfo() { } public Object clone() { try { final Object o = super.clone(); ((ChildInfo) o).vispace = null; //clean up return o; } catch (CloneNotSupportedException e) { throw new InternalError(); } } private ChildInfo clone(AbstractComponent owner) { final ChildInfo clone = (ChildInfo) clone(); AbstractComponent q = null; for (AbstractComponent p = first; p != null; p = p._next) { AbstractComponent child = (AbstractComponent) p.clone(); if (q != null) q._next = child; else clone.first = child; child._prev = q; q = child; child._parent = owner; //correct it } clone.last = q; return clone; } /** Returns whether the child is being removed. */ private boolean inRemoving(Component child) { return _aring != null && _aring[1] != null && _aring[1].contains(child); } /** Sets if the child is being removed. */ private void markRemoving(Component child, boolean set) { markARing(child, set, 1); } /** Returns whether the child is being added. */ private boolean inAdding(Component child) { return _aring != null && _aring[0] != null && _aring[0].contains(child); } /** Sets if the child is being added. */ private void markAdding(Component child, boolean set) { markARing(child, set, 0); } @SuppressWarnings("unchecked") private void markARing(Component child, boolean set, int which) { if (set) { if (_aring == null) _aring = new Set[2]; if (_aring[which] == null) _aring[which] = new HashSet(2); _aring[which].add(child); } else if (_aring != null && _aring[which] != null && _aring[which].remove(child) && _aring[which].isEmpty()) if (_aring[which == 0 ? 1 : 0] == null) //both null _aring = null; else _aring[which] = null; } } private static class ForwardInfo implements java.io.Serializable { private final EventListener listener; //List([target or targetPath, targetEvent, eventData])] private final List targets; private ForwardInfo(EventListener listener, List targets) { this.listener = listener; this.targets = targets; } } private static class TargetInfo implements java.io.Serializable { private final Object target; private final String event; private Object data; private TargetInfo(Object target, String event, Object data) { this.target = target; this.event = event; this.data = data; } } private static final Converter> _listenerInfoConverter = new Converter>() { public EventListener convert(EventListenerInfo o) { return o.listener; } }; private static class ExecInfo implements org.zkoss.zk.ui.sys.ExecutionInfo { private final Thread _thread; private final Event _event; private Method _method; private EventListener _listener; private ZScript _zscript; private ExecInfo(Event event) { _thread = Thread.currentThread(); _event = event; } public Thread getThread() { return _thread; } public Event getEvent() { return _event; } public Method getEventMethod() { return _method; } public EventListener getEventListener() { return _listener; } public ZScript getEventZScript() { return _zscript; } public void update(Method mtd, EventListener ln, ZScript zs) { _method = mtd; _listener = ln; _zscript = zs; } } // Shadow Element Implementation Start /**package*/ Map initIndexCacheMap() { Map distributedIndexInfo = getIndexCacheMap(); if (distributedIndexInfo == null) { distributedIndexInfo = new HashMap(getChildren().size()); ShadowElementsCtrl.setDistributedIndexInfo(distributedIndexInfo); } return distributedIndexInfo; } /**package*/ Map getIndexCacheMap() { return (Map) ShadowElementsCtrl.getDistributedIndexInfo(); } /**package*/ void destroyIndexCacheMap() { ShadowElementsCtrl.setDistributedIndexInfo(null); } private void triggerBeforeHostParentChanged(Component parent) { List shadowRoots = getShadowRoots(); if (!shadowRoots.isEmpty()) { try { initIndexCacheMap(); for (ShadowElement se : new LinkedList(shadowRoots)) { if (se instanceof ShadowElementCtrl) { ((ShadowElementCtrl) se).beforeHostParentChanged(parent); } } } finally { destroyIndexCacheMap(); } } } private void triggerBeforeHostChildRemoved(Component child) { List shadowRoots = getShadowRoots(); if (!shadowRoots.isEmpty()) { try { initIndexCacheMap(); final int indexOf = getChildren().indexOf(child); for (ShadowElement se : new LinkedList(shadowRoots)) { if (se instanceof ShadowElementCtrl) { ((ShadowElementCtrl) se).beforeHostChildRemoved(child, indexOf); } } } finally { destroyIndexCacheMap(); } } } private void triggerAfterHostChildRemoved(Component child) { List shadowRoots = getShadowRoots(); if (!shadowRoots.isEmpty()) { try { initIndexCacheMap(); for (ShadowElement se : new LinkedList(shadowRoots)) { if (se instanceof ShadowElementCtrl) { ((ShadowElementCtrl) se).afterHostChildRemoved(child); } } } finally { destroyIndexCacheMap(); } } } private void triggerBeforeHostChildAdded(Component child, Component insertBefore) { List shadowRoots = getShadowRoots(); if (!shadowRoots.isEmpty()) { try { initIndexCacheMap(); final int indexOfInsertBefore = insertBefore == null ? -1 : getChildren().indexOf(insertBefore); for (ShadowElement se : new LinkedList(shadowRoots)) { if (se instanceof ShadowElementCtrl) { ((ShadowElementCtrl) se).beforeHostChildAdded(child, insertBefore, indexOfInsertBefore); } } } finally { destroyIndexCacheMap(); } } } private void triggerAfterHostChildAdded(Component child) { List shadowRoots = getShadowRoots(); if (!shadowRoots.isEmpty()) { try { initIndexCacheMap(); final int indexOf = getChildren().indexOf(child); for (ShadowElement se : new LinkedList(shadowRoots)) { if (se instanceof ShadowElementCtrl) { ((ShadowElementCtrl) se).afterHostChildAdded(child, indexOf); } } } finally { destroyIndexCacheMap(); } } } // Shadow Element Implementation End @SuppressWarnings("unchecked") public List getShadowRoots() { return _auxinf != null && _auxinf.seRoots != null ? (List) _auxinf.seRoots : Collections.emptyList(); } public boolean removeShadowRoot(ShadowElement shadow) { if (_auxinf != null && _auxinf.seRoots != null) if (_auxinf.seRoots.remove(shadow)) { shadow.detach(); removeFromShadowIdMap(this, (Component) shadow); return true; } return false; } public boolean addShadowRoot(ShadowElement shadow) { if (shadow == null) { throw new IllegalArgumentException("Shadow element cannot be null!"); } if (shadow.getShadowHost() == null) { ((ShadowElementCtrl) shadow).setShadowHost(this, null); } else if (shadow.getShadowHost() != this) { throw new IllegalAccessError("The shadow element is not belonged to this host: [" + this + "]"); } AuxInfo auxinf = initAuxInfo(); if (auxinf.seRoots == null) auxinf.seRoots = new LinkedList(); //ZK-2944: this comp is shadow host, init the map if (_shadowIdMap == null) _shadowIdMap = new HashMap(4); if (!auxinf.seRoots.contains(shadow)) return auxinf.seRoots.add(shadow); return false; } public boolean addShadowRootBefore(ShadowElement shadow, ShadowElement insertBefore) { if (shadow == null) { throw new IllegalArgumentException("Shadow element cannot be null!"); } if (insertBefore == null) return addShadowRoot(shadow); if (insertBefore.getShadowHost() != this) throw new UiException("Wrong shadow host [" + insertBefore + "]"); AuxInfo auxinf = initAuxInfo(); if (!auxinf.seRoots.contains(shadow)) { auxinf.seRoots.add(auxinf.seRoots.indexOf(insertBefore), shadow); return true; } return false; } //F80 - store subtree's binder annotation count public boolean hasBindingAnnotation() { return _auxinf != null && _auxinf.hasBindingAnnot; } public boolean hasSubBindingAnnotation() { return _auxinf != null && _auxinf.subAnnotCnt > 0; } public int getSubBindingAnnotationCount() { return _auxinf == null ? 0 : _auxinf.subAnnotCnt; } protected void updateSubBindingAnnotationCount(int diff) { for (AbstractComponent node = this; node != null; node = (AbstractComponent) node.getParent()) setSubBindingAnnotationCount(diff, node); } protected void setSubBindingAnnotationCount(int diff, AbstractComponent node) { node.initAuxInfo().subAnnotCnt += diff; } public void enableBindingAnnotation() { setBindingAnnotation0(true); } public void disableBindingAnnotation() { setBindingAnnotation0(false); } private void setBindingAnnotation0(boolean hasBindingAnnot) { AuxInfo auxinf = initAuxInfo(); boolean old = auxinf.hasBindingAnnot; if (old != hasBindingAnnot) { int diff = hasBindingAnnot ? 1 : -1; auxinf.hasBindingAnnot = hasBindingAnnot; updateSubBindingAnnotationCount(diff); } } private static void addToShadowIdMap(Component comp) { if (comp instanceof ShadowElementCtrl) { Component host = ((ShadowElementCtrl) comp).getShadowHostIfAny(); if (host != null) { String id = comp.getId(); AbstractComponent ac = (AbstractComponent) host; if (ac._shadowIdMap != null) { if (ac._shadowIdMap.get(id) != null) throw new InternalError("Caller shall prevent duplicated ID for shadow hosts"); ac._shadowIdMap.put(id, (ShadowElement) comp); } } } } public ShadowElement getShadowFellowIfAny(String id) { return _shadowIdMap == null ? null : _shadowIdMap.get(id); } private static void removeFromShadowIdMap(Component comp) { if (comp instanceof ShadowElementCtrl) { Component host = ((ShadowElementCtrl) comp).getShadowHostIfAny(); if (host != null) { removeFromShadowIdMap(host, comp); } } } private static void removeFromShadowIdMap(Component host, Component comp) { if (host != null && comp instanceof ShadowElement) { String id = comp.getId(); AbstractComponent ac = (AbstractComponent) host; if (ac._shadowIdMap != null) { ac._shadowIdMap.remove(id); } } } }