📄 uivisualizer.java
字号:
/* UiVisualizer.java{{IS_NOTE Purpose: Description: History: Tue Jun 14 10:57:48 2005, Created by tomyeh}}IS_NOTECopyright (C) 2005 Potix Corporation. All Rights Reserved.{{IS_RIGHT This program is distributed under GPL Version 2.0 in the hope that it will be useful, but WITHOUT ANY WARRANTY.}}IS_RIGHT*/package org.zkoss.zk.ui.impl;import java.util.Iterator;import java.util.ListIterator;import java.util.Collection;import java.util.Collections;import java.util.List;import java.util.LinkedList;import java.util.Set;import java.util.LinkedHashSet;import java.util.HashSet;import java.util.Map;import java.util.LinkedHashMap;import java.util.HashMap;import java.util.Arrays;import java.io.StringWriter;import java.io.IOException;import org.zkoss.lang.D;import org.zkoss.lang.Objects;import org.zkoss.util.logging.Log;import org.zkoss.zk.ui.Desktop;import org.zkoss.zk.ui.Page;import org.zkoss.zk.ui.Component;import org.zkoss.zk.ui.Components;import org.zkoss.zk.ui.Execution;import org.zkoss.zk.ui.UiException;import org.zkoss.zk.ui.ext.render.Cropper;import org.zkoss.zk.ui.ext.render.ChildChangedAware;import org.zkoss.zk.ui.ext.render.MultiBranch;import org.zkoss.zk.ui.sys.Visualizer;import org.zkoss.zk.ui.sys.DesktopCtrl;import org.zkoss.zk.ui.sys.PageCtrl;import org.zkoss.zk.ui.sys.ComponentCtrl;import org.zkoss.zk.ui.sys.AbortingReason;import org.zkoss.zk.au.AuResponse;import org.zkoss.zk.au.out.*;/** * An implementation of {@link Visualizer} that works with * {@link UiEngineImpl}. * * @author tomyeh *//*package*/ class UiVisualizer implements Visualizer {// private static final Log log = Log.lookup(UiVisualizer.class); /** The first exec info that causes a chain of executions (never null). */ private final UiVisualizer _1stec; /** The associated execution. */ private final Execution _exec; /** A set of invalidated pages. */ private Set _pgInvalid; /** A set of removed pages. */ private Set _pgRemoved; /** A set of invalidated components (Component). */ private final Set _invalidated = new LinkedHashSet(37); /** A map of smart updates (Component comp, Map(String name, TimedValue(comp,name,value))). */ private final Map _smartUpdated = new HashMap(53); //we use TimedValue for better sequence control /** A set of new attached components. */ private final Set _attached = new LinkedHashSet(37); /** A set of moved components (parent changed or page changed). */ private final Set _moved = new LinkedHashSet(37); /** A map of components whose UUID is changed (Component, UUID). */ private Map _idChgd; /** A map of responses being added(Component/Page, Map(key, List/TimedValue(AuResponse))). */ private Map _responses; /** A stack of components that are including new pages (and then * become the owner of the new page, if any). */ private final List _owners; /** Time stamp for smart update and responses (see {@link TimedValue}). */ private int _timed; /** if not null, it means the current executing is aborting * and the content is reason to aborting. Its interpretation depends * on {@link org.zkoss.zk.ui.sys.UiEngine}. */ private AbortingReason _aborting; /** Whether the first execution (_1stec) is for async-update. */ private final boolean _1stau; /** Whether it is in recovering. */ private final boolean _recovering; /** Whether it is ending, i.e., no further update is allowed. */ private boolean _ending; /** * Creates a root execution (without parent). * In other words, it must be the first execution in the current request. * * @param asyncUpdate whether this execution is for async-update * @param recovering whether this execution is in recovering, * i.e., caused by {@link org.zkoss.zk.ui.sys.FailoverManager#recover}. */ public UiVisualizer(Execution exec, boolean asyncUpdate, boolean recovering) { _exec = exec; _1stec = this; _1stau = asyncUpdate; _recovering = recovering; _owners = new LinkedList(); } /** * Creates the following execution. * The first execution must use {@link #UiVisualizer(Execution, boolean)} */ public UiVisualizer(UiVisualizer parent, Execution exec) { _exec = exec; _1stec = parent._1stec; _1stau = parent._1stau; _recovering = false; _owners = null; } //-- Visualizer --// public final Execution getExecution() { return _exec; } public final boolean isEverAsyncUpdate() { return _1stau; } public final boolean addToFirstAsyncUpdate(List responses) { if (!_1stau) return false;// if (D.ON && log.finerable()) log.finer("Add to 1st au: "+responses); for (Iterator it = responses.iterator(); it.hasNext();) _1stec.addResponse(null, (AuResponse)it.next()); return true; } public boolean isRecovering() { return _recovering; } //-- update/redraw --// /** Invalidates the whole page. */ public void addInvalidate(Page page) { if (_recovering || page == null || !_exec.isAsyncUpdate(page)) return; //nothing to do if (_pgInvalid == null) _pgInvalid = new LinkedHashSet(7); _pgInvalid.add(page); } /** Adds an invalidated component. Once invalidated, all invocations * to {@link #addSmartUpdate} are ignored in this execution. */ public void addInvalidate(Component comp) { final Page page = comp.getPage(); if (_recovering || page == null || !_exec.isAsyncUpdate(page)) return; //nothing to do if (_ending) throw new IllegalStateException("ended"); checkDesktop(comp); if (_invalidated.add(comp)) _smartUpdated.remove(comp); } /** Ensure the use of component is correct. */ private void checkDesktop(Component comp) { final Desktop dt = comp.getDesktop(); if (dt != null && dt != _exec.getDesktop()) throw new IllegalStateException("Access denied: component, "+comp+", belongs to another desktop: "+dt); } /** Smart updates a component's attribute. * Meaningful only if {@link #addInvalidate(Component)} is not called in this * execution */ public void addSmartUpdate(Component comp, String attr, String value) { final Page page = comp.getPage(); if (_recovering || page == null || !_exec.isAsyncUpdate(page) || _invalidated.contains(comp)) return; //nothing to do if (_ending) throw new IllegalStateException("ended"); checkDesktop(comp); Map respmap = (Map)_smartUpdated.get(comp); if (respmap == null) _smartUpdated.put(comp, respmap = new HashMap()); respmap.put(attr, new TimedValue(_timed++, comp, attr, value)); } /** Called to update (redraw) a component, when a component is moved. * If a component's page or parent is changed, this method need to be * called only once for the top one. * * @param oldparent the parent before moved * @param oldpg the page before moved * @param newpg the page after moved */ public void addMoved(Component comp, Component oldparent, Page oldpg, Page newpg) { if (_recovering || (newpg == null && oldpg == null) || (newpg == null && !_exec.isAsyncUpdate(oldpg)) //detach from loading pg || (oldpg == null && !_exec.isAsyncUpdate(newpg))) //attach to loading pg return; //to avoid redundant AuRemove if (_ending) throw new IllegalStateException("ended"); if (oldpg == null && !_moved.contains(comp)) { //new attached _attached.add(comp); //note: we cannot examine _exec.isAsyncUpdate here because //comp.getPage might be ready when this method is called } else { if (_moved.add(comp)) { //first time added //Due to the performance of Cropper.getAvailableAtClient //usually not good, so we count on isCropper only //In other words, this algorithm might redraw something that don't //need to, but has better performance if no redudant redrawing if (oldparent != null) { final Object xc = ((ComponentCtrl)oldparent).getExtraCtrl(); if ((xc instanceof Cropper) && ((Cropper)xc).isCropper()) _invalidated.add(oldparent); } } _attached.remove(comp); } } /** Called before changing the component's UUID. * * @param addOnlyMoved if true, it is added only if it was moved * before (see {@link #addMoved}). */ public void addUuidChanged(Component comp, boolean addOnlyMoved) { if ((!addOnlyMoved || _moved.contains(comp)) && (_idChgd == null || !_idChgd.containsKey(comp))) { if (_idChgd == null) _idChgd = new LinkedHashMap(23); _idChgd.put(comp, comp.getUuid()); } } /** Adds a response directly (which will be returned when * {@link #getResponses} is called). * * <p>If the response is component-dependent, {@link AuResponse#getDepends} * must return a component. And, if the component is removed, the response * is removed, too. * * @param key could be anything. The second invocation of this method * in the same execution with the same key will override the previous one. */ public void addResponse(String key, AuResponse response) { if (response == null) throw new IllegalArgumentException(); if (_responses == null) _responses = new HashMap(); final Object depends = response.getDepends(); //Page or Component Map respmap = (Map)_responses.get(depends); if (respmap == null) _responses.put(depends, respmap = new HashMap()); final TimedValue tval = new TimedValue(_timed++, response); if (key != null) { respmap.put(key, tval); } else { List resps = (List)respmap.get(null); if (resps == null) respmap.put(null, resps = new LinkedList()); resps.add(tval); //don't overwrite } } /** Process {@link Cropper}. * * <p>Note: it is too late to handle the moved/removed components here, * since we don't know their old parent now! * Rather, we handle it in {@link #addMoved}. * * @return whether any new invalidated is added */ private boolean doCrop() { final Map cropping = new HashMap(); boolean invAdded = crop(_attached, cropping, true, false); invAdded = crop(_smartUpdated.keySet(), cropping, false, false) || invAdded; if (_responses != null) invAdded = crop(_responses.keySet(), cropping, false, true) || invAdded; invAdded = crop(_invalidated, cropping, false, false) || invAdded; //crop invalidate as the last step since new invalidated might be added return invAdded; } /** Crop attached and moved. * * @return whether any new invalidated is added */ private boolean crop(Set coll, Map cropping, boolean bAttached, boolean bResponse) { Set newInvalid = null; for (Iterator it = coll.iterator(); it.hasNext();) { final Object o = it.next(); if (!(o instanceof Component)) continue; final Component comp = (Component)o; final Page page = comp.getPage(); if (page == null || !_exec.isAsyncUpdate(page)) { if (!bResponse) it.remove(); //just in case continue; } for (Component p, c = comp; (p = c.getParent()) != null; c = p) { final Set avail = getAvailableAtClient(p, cropping); if (avail != null) { if (bAttached) { if (avail.contains(c)) { if (c != comp) continue; //not direct child, do as if not cropper //don't add to _invalidate directly since coll might //be _invalidate if (newInvalid == null) newInvalid = new HashSet(); newInvalid.add(p); } it.remove(); break; } else if (!avail.contains(c)) { it.remove(); break; } } } } return newInvalid != null && _invalidated.addAll(newInvalid); } private static Set getAvailableAtClient(Component comp, Map cropping) { final Object xc = ((ComponentCtrl)comp).getExtraCtrl(); if (xc instanceof Cropper) { //we don't need to check isCropper first since its component's job //to ensure the consistency Set set = (Set)cropping.get(comp); if (set != null) return set != Collections.EMPTY_SET ? set: null; set = ((Cropper)xc).getAvailableAtClient(); cropping.put(comp, set != null ? set: Collections.EMPTY_SET); return set; } return null; } /** Process {@link ChildChangedAware} */ private void doChildChanged() { final Set ccawares = new LinkedHashSet(), checked = new HashSet(79); doChildChanged(_invalidated, ccawares, checked); doChildChanged(_attached, ccawares, checked); doChildChanged(_smartUpdated.keySet(), ccawares, checked); if (!ccawares.isEmpty()) for (Iterator it = ccawares.iterator(); it.hasNext();) addSmartUpdate((Component)it.next(), "z.chchg", "true"); } private void doChildChanged(Collection col, Set ccawares, Set checked) { for (Iterator it = col.iterator(); it.hasNext();) { Component comp = (Component)it.next(); final Page page = comp.getPage(); if (page == null || !_exec.isAsyncUpdate(page)) continue; while ((comp = comp.getParent()) != null) { if (!checked.add(comp)) break; //already checked final Object xc = ((ComponentCtrl)comp).getExtraCtrl(); if ((xc instanceof ChildChangedAware) //&& !_invalidated.contains(comp) && !_attached.contains(comp) //No need to check _invalidated... since they are optimized && ((ChildChangedAware)xc).isChildChangedAware()) ccawares.add(comp); } } } /** Prepares {@link #_pgRemoved} to contain set of pages that will * be removed. */ private void checkPageRemoved(Set removed) { //1. scan once final Desktop desktop = _exec.getDesktop(); Set pages = null; for (Iterator it = desktop.getPages().iterator(); it.hasNext();) { final Page page = (Page)it.next(); final Component owner = ((PageCtrl)page).getOwner(); if (owner != null) { //included final Page ownerPage = owner.getPage(); if (ownerPage == null //detached || (_pgInvalid != null && _pgInvalid.contains(ownerPage)) || isAncestor(_invalidated, owner, true) || isAncestor(_attached, owner, true) || isAncestor(removed, owner, true)) { addPageRemoved(page); } else { if (pages == null) pages = new HashSet(); pages.add(page); } } } if (_pgRemoved == null || pages == null) return; //done if no page is removed or no more included page //2. if a page is ever removed, it might cause chain effect //so we have to loop until nothing changed boolean pgRemovedFound; do { pgRemovedFound = false; for (Iterator it = pages.iterator(); it.hasNext();) { final Page page = (Page)it.next(); final Component owner = ((PageCtrl)page).getOwner(); if (_pgRemoved.contains(owner.getPage())) { it.remove(); addPageRemoved(page); pgRemovedFound = true; } } } while (pgRemovedFound); //loop due to chain effect } private void addPageRemoved(Page page) { if (_pgRemoved == null) _pgRemoved = new HashSet(); _pgRemoved.add(page); if (_pgInvalid != null) _pgInvalid.remove(page);// if (D.ON && log.debugable()) log.debug("Page removed: "+page); } /** Clears components if it belongs to invalidated or removed page. */ private void clearInInvalidPage(Collection coll) { for (Iterator it = coll.iterator(); it.hasNext();) { final Component comp = (Component)it.next(); final Page page = comp.getPage(); if (page != null && ((_pgRemoved != null && _pgRemoved.contains(page)) || (_pgInvalid != null && _pgInvalid.contains(page)))) it.remove(); } } /** Returns whether any component in coll is an ancestor of comp. * @param includingEquals whether to return true if a equals B */ private boolean isAncestor(Collection coll, Component comp, boolean includingEquals) { for (Iterator it = coll.iterator(); it.hasNext();) { final Component c = (Component)it.next(); if ((includingEquals || c != comp) && Components.isAncestor(c, comp)) return true;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -