0001: /* DataBinder.java
0002:
0003: {{IS_NOTE
0004: Purpose:
0005:
0006: Description:
0007:
0008: History:
0009: Thu Feb 1 18:27:18 2007, Created by Henri
0010: }}IS_NOTE
0011:
0012: Copyright (C) 2006 Potix Corporation. All Rights Reserved.
0013:
0014: {{IS_RIGHT
0015: }}IS_RIGHT
0016: */
0017: package org.zkoss.zkplus.databind;
0018:
0019: import org.zkoss.zk.ui.Page;
0020: import org.zkoss.zk.ui.Path;
0021: import org.zkoss.zk.ui.Component;
0022: import org.zkoss.zk.ui.UiException;
0023: import org.zkoss.zk.ui.sys.ComponentCtrl;
0024: import org.zkoss.zk.ui.metainfo.Annotation;
0025: import org.zkoss.zk.ui.event.Event;
0026: import org.zkoss.zk.ui.event.Events;
0027: import org.zkoss.zk.ui.event.EventListener;
0028: import org.zkoss.zk.scripting.Namespace;
0029: import org.zkoss.zk.scripting.Interpreter;
0030: import org.zkoss.zk.scripting.HierachicalAware;
0031:
0032: import org.zkoss.zul.Comboitem;
0033: import org.zkoss.zul.Row;
0034: import org.zkoss.zul.Listitem;
0035: import org.zkoss.zul.ListModel;
0036:
0037: import org.zkoss.util.ModificationException;
0038: import org.zkoss.lang.Objects;
0039: import org.zkoss.lang.Primitives;
0040: import org.zkoss.lang.reflect.Fields;
0041:
0042: import java.util.Map;
0043: import java.util.Set;
0044: import java.util.Date;
0045: import java.util.Map.Entry;
0046: import java.util.List;
0047: import java.util.HashSet;
0048: import java.util.Iterator;
0049: import java.util.ArrayList;
0050: import java.util.ListIterator;
0051: import java.util.HashMap;
0052: import java.util.HashSet;
0053: import java.util.Collection;
0054: import java.util.LinkedHashMap;
0055: import java.util.LinkedHashSet;
0056:
0057: /**
0058: * The DataBinder used for binding ZK UI component and the backend data bean.
0059: *
0060: * @author Henri Chen
0061: */
0062: public class DataBinder {
0063: public static final String NULLIFY = "none"; //used to nullify default configuration
0064: public static final String VARNAME = "zkplus.databind.VARNAME"; //_var name
0065: public static final String TEMPLATEMAP = "zkplus.databind.TEMPLATEMAP"; // template -> clone
0066: public static final String TEMPLATE = "zkplus.databind.TEMPLATE"; //clone -> template
0067: private static final String OWNER = "zkplus.databind.OWNER"; //the collection owner of the template component
0068: private static final String HASTEMPLATEOWNER = "zkplus.databind.HASTEMPLATEOWNER"; //whether has template owner (collection in collection)
0069: private static final Object NA = new Object();
0070:
0071: private Map _compBindingMap = new LinkedHashMap(29); //(comp, Map(attr, Binding))
0072: private Map _beans = new HashMap(29); //bean local to this DataBinder
0073: private Map _beanSameNodes = new HashMap(29); //(bean, Set(BindingNode)) bean same nodes, diff expression but actually hold the same bean
0074: private BindingNode _pathTree = new BindingNode("/", false, "/",
0075: false); //path dependency tree.
0076: private boolean _defaultConfig = true; //whether load default configuration from lang-addon.xml
0077: private boolean _init; //whether this databinder is initialized.
0078: private EventListener _listener = new LoadOnSaveEventListener();
0079: //Databinder is init automatically when saveXXX or loadXxx is called
0080:
0081: protected Map _collectionItemMap = new HashMap(3);
0082:
0083: /** Binding bean to UI component. This is the same as
0084: * addBinding(Component comp, String attr, String expr, (List)null, (List)null, (String)null, (String)null).
0085: * @param comp The component to be associated.
0086: * @param attr The attribute of the component to be associated.
0087: * @param expr The expression to associate the data bean.
0088: */
0089: public void addBinding(Component comp, String attr, String expr) {
0090: addBinding(comp, attr, expr, (List) null, (List) null, null,
0091: null);
0092: }
0093:
0094: /** Binding bean to UI component.
0095: * @param comp The component to be associated.
0096: * @param attr The attribute of the component to be associated.
0097: * @param expr The expression to associate the data bean.
0098: * @param loadWhenEvents The event list when to load data.
0099: * @param saveWhenEvent The event when to save data.
0100: * @param access In the view of UI component: "load" load only,
0101: * "both" load/save, "save" save only when doing
0102: * data binding. null means using the default access natural of the component.
0103: * e.g. Label.value is "load", but Textbox.value is "both".
0104: * @param converter The converter class used to convert classes between component
0105: * and the associated bean. null means using the default class conversion method.
0106: */
0107: public void addBinding(Component comp, String attr, String expr,
0108: String[] loadWhenEvents, String saveWhenEvent,
0109: String access, String converter) {
0110: List loadEvents = null;
0111: if (loadWhenEvents != null && loadWhenEvents.length > 0) {
0112: loadEvents = new ArrayList(loadWhenEvents.length);
0113: for (int j = 0; j < loadWhenEvents.length; ++j) {
0114: loadEvents.add(loadWhenEvents[j]);
0115: }
0116: }
0117: addBinding(comp, attr, expr, loadEvents, saveWhenEvent, access,
0118: converter);
0119: }
0120:
0121: /** Binding bean to UI component.
0122: * @param comp The component to be associated.
0123: * @param attr The attribute of the component to be associated.
0124: * @param expr The expression to associate the data bean.
0125: * @param loadWhenEvents The event list when to load data.
0126: * @param saveWhenEvents The event when to save data.
0127: * @param access In the view of UI component: "load" load only,
0128: * "both" load/save, "save" save only when doing
0129: * data binding. null means using the default access natural of the component.
0130: * e.g. Label.value is "load", but Textbox.value is "both".
0131: * @param converter The converter class used to convert classes between component
0132: * and the associated bean. null means using the default class conversion method.
0133: * @since 3.0.0
0134: */
0135: public void addBinding(Component comp, String attr, String expr,
0136: String[] loadWhenEvents, String[] saveWhenEvents,
0137: String access, String converter) {
0138: List loadEvents = null;
0139: if (loadWhenEvents != null && loadWhenEvents.length > 0) {
0140: loadEvents = new ArrayList(loadWhenEvents.length);
0141: for (int j = 0; j < loadWhenEvents.length; ++j) {
0142: loadEvents.add(loadWhenEvents[j]);
0143: }
0144: }
0145: List saveEvents = null;
0146: if (saveWhenEvents != null && saveWhenEvents.length > 0) {
0147: saveEvents = new ArrayList(saveWhenEvents.length);
0148: for (int j = 0; j < saveWhenEvents.length; ++j) {
0149: saveEvents.add(saveWhenEvents[j]);
0150: }
0151: }
0152: addBinding(comp, attr, expr, loadEvents, saveEvents, access,
0153: converter);
0154: }
0155:
0156: /** Binding bean to UI component.
0157: * @param comp The component to be associated.
0158: * @param attr The attribute of the component to be associated.
0159: * @param expr The expression to associate the data bean.
0160: * @param loadWhenEvents The event list when to load data.
0161: * @param saveWhenEvent The event when to save data.
0162: * @param access In the view of UI component: "load" load only,
0163: * "both" load/save, "save" save only when doing
0164: * data binding. null means using the default access natural of the component.
0165: * e.g. Label.value is "load", but Textbox.value is "both".
0166: * @param converter The converter class used to convert classes between component
0167: * and the associated bean. null means using the default class conversion method.
0168: */
0169: public void addBinding(Component comp, String attr, String expr,
0170: List loadWhenEvents, String saveWhenEvent, String access,
0171: String converter) {
0172: List saveEvents = new ArrayList(1);
0173: saveEvents.add(saveWhenEvent);
0174: addBinding(comp, attr, expr, loadWhenEvents, saveEvents,
0175: access, converter);
0176: }
0177:
0178: /** Binding bean to UI component.
0179: * @param comp The component to be associated.
0180: * @param attr The attribute of the component to be associated.
0181: * @param expr The expression to associate the data bean.
0182: * @param loadWhenEvents The event list when to load data.
0183: * @param saveWhenEvents The event list when to save data.
0184: * @param access In the view of UI component: "load" load only,
0185: * "both" load/save, "save" save only when doing
0186: * data binding. null means using the default access natural of the component.
0187: * e.g. Label.value is "load", but Textbox.value is "both".
0188: * @param converter The converter class used to convert classes between component
0189: * and the associated bean. null means using the default class conversion method.
0190: * @since 3.0.0
0191: */
0192: public void addBinding(Component comp, String attr, String expr,
0193: List loadWhenEvents, List saveWhenEvents, String access,
0194: String converter) {
0195:
0196: //Since 2.5, 20070726, Henri Chen: we accept "each" to replace "_var" in collection data binding
0197: //Before 2.4.1
0198: //<a:bind _var="person">
0199: //<listitem...>
0200: //After 2.5
0201: //<listitem self="@{bind(each='person')}"...>
0202: //or <listitem self="@{each='person'}"...>
0203: if ("each".equals(attr)) {
0204: attr = "_var";
0205: }
0206:
0207: if (isDefaultConfig()) { //use default binding configuration
0208: //handle default-bind defined in lang-addon.xml
0209: Object[] objs = loadPropertyAnnotation(comp, attr,
0210: "default-bind");
0211:
0212: /* logically impossible to hold "expr" in default binding
0213: if (expr == null && objs[0] != null) {
0214: expr = (String) objs[0];
0215: }
0216: */
0217:
0218: if (loadWhenEvents == null && objs[1] != null) {
0219: loadWhenEvents = (List) objs[1];
0220: }
0221: if (saveWhenEvents == null && objs[2] != null) {
0222: saveWhenEvents = (List) objs[2];
0223: }
0224: if (access == null && objs[3] != null) {
0225: access = (String) objs[3];
0226: }
0227: if (converter == null && objs[4] != null) {
0228: converter = (String) objs[4];
0229: }
0230: }
0231:
0232: //nullify check
0233: boolean nullify = false;
0234: LinkedHashSet loadEvents = null;
0235: if (loadWhenEvents != null && loadWhenEvents.size() > 0) {
0236: loadEvents = new LinkedHashSet(loadWhenEvents.size());
0237: for (final Iterator it = loadWhenEvents.iterator(); it
0238: .hasNext();) {
0239: final String event = (String) it.next();
0240: if (NULLIFY.equals(event)) {
0241: loadEvents.clear();
0242: nullify = true;
0243: } else {
0244: nullify = false;
0245: loadEvents.add(event);
0246: }
0247: }
0248: if (loadEvents.isEmpty()) {
0249: loadEvents = null;
0250: }
0251: }
0252:
0253: nullify = false;
0254: Set saveEvents = null;
0255: if (saveWhenEvents != null && saveWhenEvents.size() > 0) {
0256: saveEvents = new HashSet(saveWhenEvents.size());
0257: for (final Iterator it = saveWhenEvents.iterator(); it
0258: .hasNext();) {
0259: final String event = (String) it.next();
0260: if (NULLIFY.equals(event)) {
0261: saveEvents.clear();
0262: nullify = true;
0263: } else {
0264: nullify = false;
0265: saveEvents.add(event);
0266: }
0267: }
0268: if (saveEvents.isEmpty()) {
0269: saveEvents = null;
0270: }
0271: }
0272:
0273: if (NULLIFY.equals(access)) {
0274: access = null;
0275: }
0276:
0277: if (NULLIFY.equals(converter)) {
0278: converter = null;
0279: }
0280:
0281: Map attrMap = (Map) _compBindingMap.get(comp);
0282: if (attrMap == null) {
0283: attrMap = new LinkedHashMap(3);
0284: _compBindingMap.put(comp, attrMap);
0285: }
0286:
0287: if (attrMap.containsKey(attr)) { //override
0288: final Binding binding = (Binding) attrMap.get(attr);
0289: binding.setExpression(expr);
0290: binding.setLoadWhenEvents(loadEvents);
0291: binding.setSaveWhenEvents(saveEvents);
0292: binding.setAccess(access);
0293: binding.setConverter(converter);
0294: } else {
0295: attrMap.put(attr, new Binding(this , comp, attr, expr,
0296: loadEvents, saveEvents, access, converter));
0297: }
0298: }
0299:
0300: /** Remove the binding associated with the attribute of the component.
0301: * @param comp The component to be removed the data binding association.
0302: * @param attr The attribute of the component to be removed the data binding association.
0303: */
0304: public void removeBinding(Component comp, String attr) {
0305: Map attrMap = (Map) _compBindingMap.get(comp);
0306: if (attrMap != null) {
0307: attrMap.remove(attr);
0308: }
0309: }
0310:
0311: /** Given component and attr, return the associated {@link Binding}.
0312: * @param comp the concerned component
0313: * @param attr the concerned attribute
0314: */
0315: public Binding getBinding(Component comp, String attr) {
0316: if (isClone(comp)) {
0317: comp = (Component) comp.getAttribute(TEMPLATE);
0318: }
0319: Map attrMap = (Map) _compBindingMap.get(comp);
0320: return attrMap != null ? (Binding) attrMap.get(attr) : null;
0321: }
0322:
0323: /** Given component, return the associated list of {@link Binding}s.
0324: * @param comp the concerned component
0325: */
0326: public Collection getBindings(Component comp) {
0327: if (isClone(comp)) {
0328: comp = (Component) comp.getAttribute(TEMPLATE);
0329: }
0330: Map attrMap = (Map) _compBindingMap.get(comp);
0331: return attrMap != null ? (Collection) attrMap.values() : null;
0332: }
0333:
0334: /** Whether this component associated with any bindings.
0335: */
0336: public boolean existsBindings(Component comp) {
0337: if (isClone(comp)) {
0338: comp = (Component) comp.getAttribute(TEMPLATE);
0339: }
0340: return _compBindingMap.containsKey(comp);
0341: }
0342:
0343: /** Whether this component and attribute associated with a binding.
0344: */
0345: public boolean existBinding(Component comp, String attr) {
0346: if (isClone(comp)) {
0347: comp = (Component) comp.getAttribute(TEMPLATE);
0348: }
0349: if (_compBindingMap.containsKey(comp)) {
0350: Map attrMap = (Map) _compBindingMap.get(comp);
0351: return attrMap.containsKey(attr);
0352: }
0353: return false;
0354: }
0355:
0356: /** Whether use the default binding configuration.
0357: */
0358: public boolean isDefaultConfig() {
0359: return _defaultConfig;
0360: }
0361:
0362: /** Whether use the default binding configuration.
0363: */
0364: public void setDefaultConfig(boolean b) {
0365: _defaultConfig = b;
0366: }
0367:
0368: /** Bind a real bean object to the specified beanid. You might not need to call this method because this
0369: * DataBinder would look up the variable via the {@link org.zkoss.zk.ui.Component#getVariable} method
0370: * if it cannot find the specified bean via the given beanid.
0371: *
0372: * @param beanid The bean id used in data binding.
0373: * @param bean The real bean object to be associated with the bean id.
0374: */
0375: public void bindBean(String beanid, Object bean) {
0376: _beans.put(beanid, bean);
0377: }
0378:
0379: /** Load value from the data bean property to a specified attribute of the UI component.
0380: * @param comp the UI component to be loaded value.
0381: * @param attr the UI component attribute to be loaded value.
0382: */
0383: public void loadAttribute(Component comp, String attr) {
0384: if (isTemplate(comp) || comp.getPage() == null) {
0385: return; //skip detached component
0386: }
0387: init();
0388: Binding binding = getBinding(comp, attr);
0389: if (binding != null) {
0390: binding.loadAttribute(comp);
0391: }
0392: }
0393:
0394: /** Save value from a specified attribute of the UI component to a data bean property.
0395: * @param comp the UI component used to save value into backend data bean.
0396: * @param attr the UI component attribute used to save value into backend data bean.
0397: */
0398: public void saveAttribute(Component comp, String attr) {
0399: if (isTemplate(comp) || comp.getPage() == null) {
0400: return; //skip detached component
0401: }
0402: init();
0403: Binding binding = getBinding(comp, attr);
0404: if (binding != null) {
0405: binding.saveAttribute(comp);
0406: }
0407: }
0408:
0409: /** Load values from the data bean properties to all attributes of a specified UI component.
0410: * @param comp the UI component to be loaded value.
0411: */
0412: public void loadComponent(Component comp) {
0413: if (isTemplate(comp) || comp.getPage() == null) {
0414: return; //skip detached component
0415: }
0416: init();
0417: Collection bindings = getBindings(comp);
0418: if (bindings != null) {
0419: loadAttrs(comp, bindings);
0420: }
0421:
0422: //load kids of this component
0423: for (final Iterator it = comp.getChildren().iterator(); it
0424: .hasNext();) {
0425: loadComponent((Component) it.next()); //recursive
0426: }
0427: }
0428:
0429: /** Save values from all attributes of a specified UI component to data bean properties.
0430: * @param comp the UI component used to save value into backend data bean.
0431: */
0432: public void saveComponent(Component comp) {
0433: if (isTemplate(comp) || comp.getPage() == null) {
0434: return; //skip detached component
0435: }
0436: init();
0437: Collection bindings = getBindings(comp);
0438: if (bindings != null) {
0439: saveAttrs(comp, bindings);
0440: }
0441:
0442: //save kids of this component
0443: for (final Iterator it = comp.getChildren().iterator(); it
0444: .hasNext();) {
0445: saveComponent((Component) it.next()); //recursive
0446: }
0447: }
0448:
0449: /** Load all value from data beans to UI components. */
0450: public void loadAll() {
0451: init();
0452: for (final Iterator it = _compBindingMap.keySet().iterator(); it
0453: .hasNext();) {
0454: final Component comp = (Component) it.next();
0455: loadComponent(comp);
0456: }
0457: }
0458:
0459: /** Save all values from UI components to beans. */
0460: public void saveAll() {
0461: init();
0462: for (final Iterator it = _compBindingMap.keySet().iterator(); it
0463: .hasNext();) {
0464: final Component comp = (Component) it.next();
0465: saveComponent(comp);
0466: }
0467: }
0468:
0469: private void loadAttrs(String expr, Collection attrs) {
0470: for (final Iterator it = attrs.iterator(); it.hasNext();) {
0471: Binding binding = (Binding) it.next();
0472: Component comp = binding.getComponent();
0473: binding.loadAttribute(comp, expr);
0474: }
0475: }
0476:
0477: private void loadAttrs(Component comp, Collection attrs) {
0478: for (final Iterator it = attrs.iterator(); it.hasNext();) {
0479: Binding binding = (Binding) it.next();
0480: binding.loadAttribute(comp);
0481: }
0482: }
0483:
0484: private void saveAttrs(Component comp, Collection attrs) {
0485: for (final Iterator it = attrs.iterator(); it.hasNext();) {
0486: Binding binding = (Binding) it.next();
0487: binding.saveAttribute(comp);
0488: }
0489: }
0490:
0491: //[0] expr, [1] loadWhenEvents, [2] saveWhenEvents, [3] access, [4] converter
0492: protected Object[] loadPropertyAnnotation(Component comp,
0493: String propName, String bindName) {
0494: ComponentCtrl compCtrl = (ComponentCtrl) comp;
0495: Annotation ann = compCtrl.getAnnotation(propName, bindName);
0496: if (ann != null) {
0497: final Map attrs = ann.getAttributes(); //(tag, tagExpr)
0498: List loadWhenEvents = null;
0499: List saveWhenEvents = null;
0500: String access = null;
0501: String converter = null;
0502: String expr = null;
0503: for (final Iterator it = attrs.entrySet().iterator(); it
0504: .hasNext();) {
0505: Map.Entry entry = (Map.Entry) it.next();
0506: String tag = (String) entry.getKey();
0507: String tagExpr = (String) entry.getValue();
0508: if ("save-when".equals(tag)) {
0509: saveWhenEvents = parseExpression(tagExpr, ",");
0510: } else if ("access".equals(tag)) {
0511: access = tagExpr;
0512: } else if ("converter".equals(tag)) {
0513: converter = tagExpr;
0514: } else if ("load-when".equals(tag)) {
0515: loadWhenEvents = parseExpression(tagExpr, ",");
0516: } else if ("value".equals(tag)) {
0517: expr = tagExpr;
0518: }
0519: }
0520: return new Object[] { expr, loadWhenEvents, saveWhenEvents,
0521: access, converter };
0522: }
0523: return new Object[5];
0524: }
0525:
0526: //late init
0527: protected void init() {
0528: if (!_init) {
0529: _init = true;
0530:
0531: // init CollectionItem
0532: initCollectionItem();
0533:
0534: //setup all added bindings
0535: final Set varnameSet = new HashSet();
0536: final LinkedHashSet toBeDetached = new LinkedHashSet();
0537: for (final Iterator it = _compBindingMap.entrySet()
0538: .iterator(); it.hasNext();) {
0539: final Entry me = (Entry) it.next();
0540: final Component comp = (Component) me.getKey();
0541: final Map attrMap = (Map) me.getValue();
0542: final Collection bindings = attrMap.values();
0543:
0544: //_var special case; meaning a template component
0545: if (attrMap.containsKey("_var")) {
0546: setupTemplateComponent(comp,
0547: getComponentCollectionOwner(comp)); //setup as template components
0548: String varname = ((Binding) attrMap.get("_var"))
0549: .getExpression();
0550: varnameSet.add(varname);
0551: comp.setAttribute(VARNAME, varname);
0552: setupBindingRenderer(comp); //setup binding renderer
0553: toBeDetached.add(comp);
0554: }
0555:
0556: if (bindings != null) {
0557: //construct the path dependant tree
0558: setupPathTree(bindings, varnameSet);
0559:
0560: //register save-when event
0561: registerSaveEvents(comp, bindings);
0562:
0563: //register load-when events
0564: registerLoadEvents(comp, bindings);
0565: }
0566: }
0567:
0568: //detach template components so they will not interfer the visual part
0569: for (final Iterator it = toBeDetached.iterator(); it
0570: .hasNext();) {
0571: final Component comp = (Component) it.next();
0572: comp.detach();
0573: }
0574: }
0575: }
0576:
0577: private void initCollectionItem() {
0578: addCollectionItem(Listitem.class.getName(),
0579: new ListitemCollectionItem());
0580: addCollectionItem(Row.class.getName(), new RowCollectionItem());
0581: addCollectionItem(Comboitem.class.getName(),
0582: new ComboitemCollectionItem());
0583: }
0584:
0585: /**
0586: * Adds a CollectionItem for this comp.
0587: * @see CollectionItem
0588: * @since 3.0.0
0589: */
0590: public void addCollectionItem(String comp, CollectionItem decor) {
0591: _collectionItemMap.put(comp, decor);
0592: }
0593:
0594: //get Collection owner of a given collection item.
0595: private Component getComponentCollectionOwner(Component comp) {
0596: CollectionItem decor = getBindingCollectionItem(comp);
0597: return decor.getComponentCollectionOwner(comp);
0598: }
0599:
0600: /**
0601: * Returns a CollectionItem by the comp accordingly.
0602: * @see CollectionItem
0603: * @since 3.0.0
0604: */
0605: protected CollectionItem getBindingCollectionItem(Component comp) {
0606: String name = comp.getClass().getName();
0607: if (comp instanceof Listitem) {
0608: name = Listitem.class.getName();
0609: } else if (comp instanceof Row) {
0610: name = Row.class.getName();
0611: }
0612: CollectionItem decorName = (CollectionItem) _collectionItemMap
0613: .get(name);
0614: if (decorName != null) {
0615: return decorName;
0616: } else {
0617: throw new UiException(
0618: "Cannot find associated CollectionItem:" + comp);
0619: }
0620: }
0621:
0622: //get Collection owner of a given collection item.
0623: private Component getCollectionOwner(Component comp) {
0624: if (isTemplate(comp)) {
0625: return (Component) comp.getAttribute(OWNER);
0626: }
0627: return getComponentCollectionOwner(comp);
0628: }
0629:
0630: //get associated clone of a given bean and template component
0631: private Component getCollectionItem(Component comp, Object bean) {
0632: Component owner = getCollectionOwner(comp);
0633: CollectionItem decor = getBindingCollectionItem(comp);
0634: final ListModel xmodel = decor.getModelByOwner(owner);
0635: if (xmodel instanceof BindingListModel) {
0636: final BindingListModel model = (BindingListModel) xmodel;
0637: int index = model.indexOf(bean);
0638: if (index >= 0) {
0639: return lookupClone(decor.getComponentAtIndexByOwner(
0640: owner, index), comp);
0641: }
0642: }
0643: return null;
0644: }
0645:
0646: //set the binding renderer for the template listitem component
0647: private void setupBindingRenderer(Component comp) {
0648: getBindingCollectionItem(comp).setupBindingRenderer(comp, this );
0649: }
0650:
0651: private void setupPathTree(Collection bindings, Set varnameSet) {
0652: for (final Iterator it = bindings.iterator(); it.hasNext();) {
0653: final Binding binding = (Binding) it.next();
0654: String[] paths = binding.getPaths();
0655: for (int j = 0; j < paths.length; ++j) {
0656: final String path = (String) paths[j];
0657: _pathTree.addBinding(path, binding, varnameSet);
0658: }
0659: }
0660: }
0661:
0662: private void registerSaveEvents(Component comp, Collection bindings) {
0663: for (final Iterator it = bindings.iterator(); it.hasNext();) {
0664: final Binding binding = (Binding) it.next();
0665: binding.registerSaveEvents(comp);
0666: }
0667: }
0668:
0669: private void registerLoadEvents(Component comp, Collection bindings) {
0670: for (final Iterator it = bindings.iterator(); it.hasNext();) {
0671: final Binding binding = (Binding) it.next();
0672: binding.registerLoadEvents(comp);
0673: }
0674: }
0675:
0676: //whether exists the specified bean in this DataBinder.
0677: /* package */boolean existsBean(String beanid) {
0678: return _beans.containsKey(beanid);
0679: }
0680:
0681: //get a bean by the beanid from this Data binder
0682: /* package */Object getBean(String beanid) {
0683: return _beans.get(beanid);
0684: }
0685:
0686: //set a bean into this Data binder
0687: /* package */void setBean(String beanid, Object bean) {
0688: _beans.put(beanid, bean);
0689: }
0690:
0691: /**
0692: * Sets up the specified comp and its decendents to be as template (or not)
0693: */
0694: public void setupTemplateComponent(Component comp, Object owner) {
0695: if (existsBindings(comp)) {
0696: if (comp.getAttribute(OWNER) != null) {
0697: comp.setAttribute(HASTEMPLATEOWNER, Boolean.TRUE); //owner is a template
0698: }
0699: comp.setAttribute(OWNER, owner);
0700: }
0701: List kids = comp.getChildren();
0702: for (final Iterator it = kids.iterator(); it.hasNext();) {
0703: setupTemplateComponent((Component) it.next(), owner); //recursive
0704: }
0705: }
0706:
0707: //parse token and return as a List of String
0708: /* package */static List parseExpression(String expr,
0709: String separator) {
0710: if (expr == null) {
0711: return null;
0712: }
0713: List results = new ArrayList(5);
0714: while (true) {
0715: int j = expr.indexOf(separator);
0716: if (j < 0) {
0717: results.add(expr.trim());
0718: return results;
0719: }
0720: results.add(expr.substring(0, j).trim());
0721:
0722: if (expr.length() <= (j + 1)) {
0723: return results;
0724: }
0725: expr = expr.substring(j + 1);
0726: }
0727: }
0728:
0729: //whether a component is a binding template rather than a real component
0730: /* package */static boolean isTemplate(Component comp) {
0731: return comp.getAttribute(OWNER) != null;
0732: }
0733:
0734: //whether a cloned component from the template.
0735: /* package */static boolean isClone(Component comp) {
0736: //bug #1813055 Multiple listboxes with same selectedItem causes NPE
0737: return comp != null
0738: && (comp.getAttribute(TEMPLATE) instanceof Component);
0739: }
0740:
0741: //whether has template owner (collection in collection)
0742: /* package */static boolean hasTemplateOwner(Component comp) {
0743: //bug #1813055 Multiple listboxes with same selectedItem causes NPE
0744: return comp != null
0745: && (comp.getAttribute(HASTEMPLATEOWNER) != null);
0746: }
0747:
0748: //set a bean to SameNode Set
0749: /* package */void setBeanSameNodes(Object bean, Set set) {
0750: _beanSameNodes.put(bean, set);
0751: }
0752:
0753: //get SameNode Set of the given bean
0754: /* package */Set getBeanSameNodes(Object bean) {
0755: return (Set) _beanSameNodes.get(bean);
0756: }
0757:
0758: //remove SameNode set of the given bean
0759: /* package */Set removeBeanSameNodes(Object bean) {
0760: return (Set) _beanSameNodes.remove(bean);
0761: }
0762:
0763: /** traverse the path nodes and return the final bean.
0764: */
0765: /* package */Object getBeanAndRegisterBeanSameNodes(
0766: Component comp, String path) {
0767: return myGetBeanWithExpression(comp, path, true);
0768: }
0769:
0770: private Object getBeanWithExpression(Component comp, String path) {
0771: return myGetBeanWithExpression(comp, path, false);
0772: }
0773:
0774: private Object myGetBeanWithExpression(Component comp, String path,
0775: boolean registerNode) {
0776: Object bean = null;
0777: BindingNode currentNode = _pathTree;
0778: final List nodeids = parseExpression(path, ".");
0779: final Iterator it = nodeids.iterator();
0780: if (it != null && it.hasNext()) {
0781: String nodeid = (String) it.next();
0782: currentNode = (BindingNode) currentNode.getKidNode(nodeid);
0783: if (currentNode == null) {
0784: throw new UiException(
0785: "Cannot find the specified databind bean expression:"
0786: + path);
0787: }
0788: bean = lookupBean(comp, nodeid);
0789: if (registerNode) {
0790: registerBeanNode(bean, currentNode);
0791: }
0792: } else {
0793: throw new UiException(
0794: "Incorrect format of databind bean expression:"
0795: + path);
0796: }
0797:
0798: while (bean != null && it.hasNext()) {
0799: String nodeid = (String) it.next();
0800: currentNode = (BindingNode) currentNode.getKidNode(nodeid);
0801: if (currentNode == null) {
0802: throw new UiException(
0803: "Cannot find the specified databind bean expression:"
0804: + path);
0805: }
0806: bean = fetchValue(bean, currentNode, nodeid, registerNode);
0807: }
0808:
0809: return bean;
0810: }
0811:
0812: private Object fetchValue(Object bean, BindingNode node,
0813: String nodeid, boolean registerNode) {
0814: if (bean != null) {
0815: try {
0816: bean = Fields.get(bean, nodeid);
0817: } catch (NoSuchMethodException ex) {
0818: //feature#1766905 Binding to Map
0819: if (bean instanceof Map) {
0820: bean = ((Map) bean).get(nodeid);
0821: } else {
0822: throw UiException.Aide.wrap(ex);
0823: }
0824: }
0825: }
0826: if (registerNode) {
0827: registerBeanNode(bean, node);
0828: }
0829: return bean;
0830: }
0831:
0832: /* package */void setBeanAndRegisterBeanSameNodes(Component comp,
0833: Object val, Binding binding, String path,
0834: boolean autoConvert, Object rawval, List loadOnSaveInfos) {
0835: Object orgVal = null;
0836: Object bean = null;
0837: BindingNode currentNode = _pathTree;
0838: boolean refChanged = false; //wether this setting change the reference
0839: String beanid = null;
0840: final List nodeids = parseExpression(path, ".");
0841: final List nodes = new ArrayList(nodeids.size());
0842: final Iterator it = nodeids.iterator();
0843: if (it != null && it.hasNext()) {
0844: beanid = (String) it.next();
0845: currentNode = (BindingNode) currentNode.getKidNode(beanid);
0846: if (currentNode == null) {
0847: throw new UiException(
0848: "Cannot find the specified databind bean expression:"
0849: + path);
0850: }
0851: nodes.add(currentNode);
0852: bean = lookupBean(comp, beanid);
0853: } else {
0854: throw new UiException(
0855: "Incorrect format of databind bean expression:"
0856: + path);
0857: }
0858:
0859: if (!it.hasNext()) { //assign back to where bean is stored
0860: orgVal = bean;
0861: if (Objects.equals(orgVal, val)) {
0862: return; //same value, no need to do anything
0863: }
0864: if (existsBean(beanid)) {
0865: setBean(beanid, val);
0866: } else if (!setZScriptVariable(comp, beanid, val)) {
0867: comp.setVariable(beanid, val, false);
0868: }
0869: refChanged = true;
0870: } else {
0871: if (bean == null) {
0872: return; //no bean to set value, skip
0873: }
0874: int sz = nodeids.size() - 2; //minus first and last beanid in path
0875: for (; bean != null && it.hasNext() && sz > 0; --sz) {
0876: beanid = (String) it.next();
0877: currentNode = (BindingNode) currentNode
0878: .getKidNode(beanid);
0879: if (currentNode == null) {
0880: throw new UiException(
0881: "Cannot find the specified databind bean expression:"
0882: + path);
0883: }
0884: nodes.add(currentNode);
0885: try {
0886: bean = Fields.get(bean, beanid);
0887: } catch (NoSuchMethodException ex) {
0888: //feature#1766905 Binding to Map
0889: if (bean instanceof Map) {
0890: bean = ((Map) bean).get(beanid);
0891: } else {
0892: throw UiException.Aide.wrap(ex);
0893: }
0894: }
0895: }
0896: if (bean == null) {
0897: return; //no bean to set value, skip
0898: }
0899: beanid = (String) it.next();
0900: try {
0901: orgVal = Fields.get(bean, beanid);
0902: if (Objects.equals(orgVal, val)) {
0903: return; //same value, no need to do anything
0904: }
0905: Fields.set(bean, beanid, val, autoConvert);
0906: } catch (NoSuchMethodException ex) {
0907: //feature#1766905 Binding to Map
0908: if (bean instanceof Map) {
0909: ((Map) bean).put(beanid, val);
0910: } else {
0911: throw UiException.Aide.wrap(ex);
0912: }
0913: } catch (ModificationException ex) {
0914: throw UiException.Aide.wrap(ex);
0915: }
0916: if (!isPrimitive(val) && !isPrimitive(orgVal)) { //val is a bean (null is not primitive)
0917: currentNode = (BindingNode) currentNode
0918: .getKidNode(beanid);
0919: if (currentNode == null) {
0920: throw new UiException(
0921: "Cannot find the specified databind bean expression:"
0922: + path);
0923: }
0924: nodes.add(currentNode);
0925: bean = orgVal;
0926: refChanged = true;
0927: }
0928: }
0929:
0930: if (val != null) {
0931: if (refChanged && !binding.isLoadable()
0932: && binding.isSavable()) { //the sameNodes should change accordingly.
0933: registerBeanNode(val, currentNode);
0934: }
0935: //20070309, Henri Chen: Tricky.
0936: //When loading page, listbox.selectedItem == null. The _var Listitem will not be able to
0937: //associate with the selectedItem (no way to associate via null bean). When end user then
0938: //select one Listitem, we have to force such association.
0939: if (rawval instanceof Component) {
0940: Binding varbinding = getBinding((Component) rawval,
0941: "_var");
0942: if (varbinding != null) {
0943: registerBeanNode(val, currentNode);
0944: getBeanAndRegisterBeanSameNodes((Component) rawval,
0945: varbinding.getExpression());
0946: }
0947: }
0948: }
0949:
0950: //TODO:Henri Chen: Is it possible to make the loadOnSave event to be called once only for a
0951: //setXxx. So avoid load a node several times?
0952:
0953: //register "onLoadSave" listener to this component if have not done so.
0954: if (!comp.isListenerAvailable("onLoadOnSave", true)) {
0955: comp.addEventListener("onLoadOnSave", _listener);
0956: }
0957:
0958: Object[] loadOnSaveInfo = new Object[] { this , currentNode,
0959: binding, (refChanged ? val : bean),
0960: Boolean.valueOf(refChanged), nodes, comp };
0961: if (loadOnSaveInfos != null) {
0962: loadOnSaveInfos.add(loadOnSaveInfo);
0963: } else {
0964: //do loadOnSave immediately
0965: Events.postEvent(new Event("onLoadOnSave", comp,
0966: loadOnSaveInfo));
0967: }
0968: }
0969:
0970: private void registerBeanNode(Object bean, BindingNode node) {
0971: if (isPrimitive(bean)) {
0972: return;
0973: }
0974: final Set nodeSameNodes = node.getSameNodes();
0975: final Set binderSameNodes = getBeanSameNodes(bean);
0976: //variable node(with _var) is special. Assume selectedItem then _var.
0977: //e.g. a Listitem but no selectedItem yet
0978: if (node.isVar() && binderSameNodes == null) {
0979: return;
0980: }
0981:
0982: if (!nodeSameNodes.contains(bean)) {
0983: //remove the old bean
0984: for (final Iterator it = nodeSameNodes.iterator(); it
0985: .hasNext();) {
0986: final Object elm = it.next();
0987: if (!(elm instanceof BindingNode)) {
0988: it.remove();
0989: removeBeanSameNodes(elm); //remove the binderSameNodes of the original bean
0990: break;
0991: }
0992: }
0993: //add the new bean if not null
0994: if (bean != null) {
0995: nodeSameNodes.add(bean);
0996: }
0997: }
0998:
0999: if (binderSameNodes == null) {
1000: if (bean != null) {
1001: setBeanSameNodes(bean, nodeSameNodes);
1002: }
1003: } else {
1004: node.mergeAndSetSameNodes(binderSameNodes);
1005: }
1006:
1007: }
1008:
1009: private boolean isPrimitive(Object bean) {
1010: //String is deemed as primitive and null is not primitive
1011: return (bean instanceof String)
1012: || (bean != null && Primitives.toPrimitive(bean
1013: .getClass()) != null) || (bean instanceof Date)
1014: || (bean instanceof Number);
1015: }
1016:
1017: /** Sets the variable to all loaded interpreters, if it was defined in
1018: * the interpreter.
1019: *
1020: * @return whether it is set to the interpreter
1021: */
1022: private boolean setZScriptVariable(Component comp, String beanid,
1023: Object val) {
1024: //for all loaded interperter, assign val to beanid
1025: boolean found = false;
1026: final Namespace ns = comp.getNamespace();
1027: for (final Iterator it = comp.getPage().getLoadedInterpreters()
1028: .iterator(); it.hasNext();) {
1029: final Interpreter ip = (Interpreter) it.next();
1030: if (ip instanceof HierachicalAware) {
1031: final HierachicalAware ha = (HierachicalAware) ip;
1032: if (ha.containsVariable(ns, beanid)) {
1033: ha.setVariable(ns, beanid, val);
1034: found = true;
1035: }
1036: } else if (ip.containsVariable(beanid)) {
1037: ip.setVariable(beanid, val);
1038: found = true;
1039: }
1040: }
1041: return found;
1042: }
1043:
1044: /*package*/Object lookupBean(Component comp, String beanid) {
1045: //fetch the bean object
1046: Object bean = null;
1047:
1048: //bug#1871833: Data binder should read "self".
1049: if ("self".equals(beanid)) {
1050: return comp;
1051: }
1052: if (isClone(comp)) {
1053: bean = myLookupBean1(comp, beanid);
1054: if (bean != NA) {
1055: return bean;
1056: }
1057: }
1058: if (existsBean(beanid)) {
1059: bean = getBean(beanid);
1060: } else if (beanid.startsWith("/")) { //a absolute component Path: // or /
1061: bean = Path.getComponent(beanid);
1062: } else if (beanid.startsWith(".")) { //a relative component Path: ./ or ../
1063: bean = Path.getComponent(comp.getSpaceOwner(), beanid);
1064: } else {
1065: final Page page = comp.getPage();
1066: if (page != null)
1067: bean = page.getZScriptVariable(comp.getNamespace(),
1068: beanid);
1069: if (bean == null)
1070: bean = comp.getVariable(beanid, false);
1071: }
1072: return bean;
1073: }
1074:
1075: //given a beanid and a template, return the associated bean
1076: //return NA if cannot find it
1077: private Object myLookupBean1(Component comp, String beanid) {
1078: Map templatemap = (Map) comp.getAttribute(TEMPLATEMAP);
1079: return myLookupBean2(beanid, templatemap);
1080: }
1081:
1082: private Object myLookupBean2(String beanid, Map templatemap) {
1083: if (templatemap != null) {
1084: if (templatemap.containsKey(beanid)) { //got it
1085: return templatemap.get(beanid);
1086: } else { //search up the parent templatemap
1087: templatemap = (Map) templatemap.get(TEMPLATEMAP);
1088: return myLookupBean2(beanid, templatemap); //recursive
1089: }
1090: }
1091: return NA; //not available
1092: }
1093:
1094: //given a clone and a template, return the associated clone of that template.
1095: /*package*/static Component lookupClone(Component srcClone,
1096: Component srcTemplate) {
1097: if (isTemplate(srcTemplate)) {
1098: Map templatemap = (Map) srcClone.getAttribute(TEMPLATEMAP);
1099: return myLookupClone(srcTemplate, templatemap);
1100: }
1101: return null;
1102: }
1103:
1104: private static Component myLookupClone(Component srcTemplate,
1105: Map templatemap) {
1106: if (templatemap != null) {
1107: if (templatemap.containsKey(srcTemplate)) { //got it
1108: return (Component) templatemap.get(srcTemplate);
1109: } else { //search up the parent templatemap
1110: templatemap = (Map) templatemap.get(TEMPLATEMAP);
1111: return myLookupClone(srcTemplate, templatemap); //recursive
1112: }
1113: }
1114: return null;
1115: }
1116:
1117: // Given parentNode, path, and level, return associate same kid nodes of parent
1118: // a1.b.c -> a2.b.c, a3.b.c, ...
1119: private Set getAssociateSameNodes(BindingNode parentNode,
1120: String path, int level) {
1121: final List nodeids = DataBinder.parseExpression(path, ".");
1122: final int sz = nodeids.size();
1123: final List subids = nodeids.subList(sz - level, sz);
1124:
1125: Object bean = null;
1126: for (final Iterator it = parentNode.getSameNodes().iterator(); it
1127: .hasNext();) {
1128: Object obj = it.next();
1129: if (!(obj instanceof BindingNode)) {
1130: bean = obj;
1131: break;
1132: }
1133: }
1134:
1135: //for each same node, find the associated kid node
1136: final Set assocateSameNodes = new HashSet();
1137: for (final Iterator it = parentNode.getSameNodes().iterator(); it
1138: .hasNext();) {
1139: //locate the associate kid node
1140: BindingNode currentNode = null;
1141: final Object obj = it.next();
1142: if (!(obj instanceof BindingNode)
1143: || currentNode == parentNode) {
1144: continue;
1145: }
1146:
1147: currentNode = (BindingNode) obj;
1148: for (final Iterator itx = subids.iterator(); itx.hasNext();) {
1149: final String nodeid = (String) itx.next();
1150: currentNode = (BindingNode) currentNode
1151: .getKidNode(nodeid);
1152: if (currentNode == null) {
1153: break;
1154: }
1155: }
1156:
1157: if (currentNode != null) {
1158: if (!currentNode.isVar()) {
1159: assocateSameNodes.add(currentNode);
1160: } else { //a var node, specialcase, find the var root
1161: Component varRootComp = getVarRootComponent(currentNode);
1162: assocateSameNodes.add(new Object[] { currentNode,
1163: varRootComp });
1164: }
1165: }
1166: }
1167: return assocateSameNodes;
1168: }
1169:
1170: private Component getVarRootComponent(BindingNode node) {
1171: final BindingNode varRootNode = node.getRootNode(_pathTree);
1172:
1173: Object bean = null;
1174: for (final Iterator it = varRootNode.getSameNodes().iterator(); it
1175: .hasNext();) {
1176: Object obj = it.next();
1177: if (!(obj instanceof BindingNode)) {
1178: bean = obj;
1179: break;
1180: }
1181: }
1182:
1183: Component comp = null;
1184: for (final Iterator itx = varRootNode.getBindings().iterator(); itx
1185: .hasNext();) {
1186: Binding binding = (Binding) itx.next();
1187: if ("_var".equals(binding.getAttr())) {
1188: comp = binding.getComponent();
1189: break;
1190: }
1191: }
1192:
1193: return getCollectionItem(comp, bean);
1194: }
1195:
1196: private class LoadOnSaveEventListener implements EventListener {
1197: public LoadOnSaveEventListener() {
1198: }
1199:
1200: //-- EventListener --//
1201: public void onEvent(Event event) {
1202: final Set walkedNodes = new HashSet(32);
1203: final Set loadedBindings = new HashSet(32 * 2);
1204:
1205: Object obj = event.getData();
1206: if (obj instanceof List) {
1207: for (final Iterator it = ((List) obj).iterator(); it
1208: .hasNext();) {
1209: final Object[] data = (Object[]) it.next();
1210: doLoad(data, walkedNodes, loadedBindings);
1211: }
1212: } else {
1213: doLoad((Object[]) obj, walkedNodes, loadedBindings);
1214: }
1215: }
1216:
1217: private void doLoad(Object[] data, Set walkedNodes,
1218: Set loadedBindings) {
1219: if (!data[0].equals(DataBinder.this )) {
1220: return; //not for this DataBinder, skip
1221: }
1222: final BindingNode node = (BindingNode) data[1]; //to be loaded nodes
1223: final Binding savebinding = (Binding) data[2]; //to be excluded binding
1224: final Object bean = data[3]; //saved bean
1225: final boolean refChanged = ((Boolean) data[4])
1226: .booleanValue(); //whether bean itself changed
1227: final List nodes = (List) data[5]; //the complete nodes along the path to the node
1228: final Component savecomp = (Component) data[6]; //saved comp that trigger this load-on-save event
1229: if (savecomp != null) {
1230: loadAllNodes(bean, node, savecomp, savebinding,
1231: refChanged, nodes, walkedNodes, loadedBindings);
1232: }
1233: }
1234:
1235: /** Load all associated BindingNodes below the given nodes (depth first traverse).
1236: */
1237: private void loadAllNodes(Object bean, BindingNode node,
1238: Component collectionComp, Binding savebinding,
1239: boolean refChanged, List nodes, Set walkedNodes,
1240: Set loadedBindings) {
1241: myLoadAllNodes(bean, node, collectionComp, walkedNodes,
1242: savebinding, loadedBindings, refChanged);
1243:
1244: //for each ancestor, find associated same nodes
1245: if (!nodes.isEmpty()) {
1246: final String path = node.getPath();
1247: int level = 1;
1248: for (final ListIterator it = nodes.listIterator(nodes
1249: .size() - 1); it.hasPrevious(); ++level) {
1250: final BindingNode parentNode = (BindingNode) it
1251: .previous();
1252: final Set associateSameNodes = getAssociateSameNodes(
1253: parentNode, path, level);
1254: for (final Iterator itx = associateSameNodes
1255: .iterator(); itx.hasNext();) {
1256: Object obj = itx.next();
1257: if (obj instanceof BindingNode) {
1258: BindingNode samenode = (BindingNode) obj;
1259: myLoadAllNodes(bean, samenode,
1260: collectionComp, walkedNodes,
1261: savebinding, loadedBindings,
1262: refChanged);
1263: } else {
1264: BindingNode samenode = (BindingNode) ((Object[]) obj)[0];
1265: Component varRootComp = (Component) ((Object[]) obj)[1];
1266: myLoadAllNodes(bean, samenode, varRootComp,
1267: walkedNodes, savebinding,
1268: loadedBindings, refChanged);
1269:
1270: }
1271: }
1272: }
1273: }
1274: }
1275:
1276: private void myLoadAllNodes(Object bean, BindingNode node,
1277: Component collectionComp, Set walkedNodes,
1278: Binding savebinding, Set loadedBindings,
1279: boolean refChanged) {
1280: if (walkedNodes.contains(node)) {
1281: return; //already walked, skip
1282: }
1283: //mark as walked already
1284: walkedNodes.add(node);
1285:
1286: //the component might have been removed
1287: if (collectionComp == null) {
1288: return;
1289: }
1290: //loading
1291: collectionComp = loadBindings(bean, node, collectionComp,
1292: savebinding, loadedBindings, refChanged);
1293:
1294: for (final Iterator it = node.getKidNodes().iterator(); it
1295: .hasNext();) {
1296: final BindingNode kidnode = (BindingNode) it.next();
1297: final Object kidbean = fetchValue(bean, kidnode,
1298: kidnode.getNodeId(), true);
1299: myLoadAllNodes(kidbean, kidnode, collectionComp,
1300: walkedNodes, savebinding, loadedBindings, true); //recursive
1301: }
1302:
1303: for (final Iterator it = new ArrayList(node.getSameNodes())
1304: .iterator(); it.hasNext();) {
1305: final Object obj = it.next();
1306: if (obj instanceof BindingNode) {
1307: final BindingNode samenode = (BindingNode) obj;
1308: if (node == samenode) {
1309: continue;
1310: }
1311: if (samenode.isVar()) { // -> var node
1312: //var node must traverse from the root
1313: //even a root, must make sure the samebean (could be diff)
1314: //even the same bean, if a inner var root(collection in collection), not a real root
1315: if (!samenode.isRoot()
1316: || !isSameBean(samenode, bean)
1317: || samenode.isInnerCollectionNode()) {
1318: continue;
1319: }
1320: } else if (node.isVar()
1321: && !isSameBean(samenode, bean)) { //var -> !var, must same bean
1322: continue;
1323: }
1324: myLoadAllNodes(bean, samenode, collectionComp,
1325: walkedNodes, savebinding, loadedBindings,
1326: refChanged); //recursive
1327: }
1328: }
1329: }
1330:
1331: //return nearest collection item Component (i.e. Listitem)
1332: private Component loadBindings(Object bean, BindingNode node,
1333: Component collectionComp, Binding savebinding,
1334: Set loadedBindings, boolean refChanged) {
1335: final Collection bindings = node.getBindings();
1336: for (final Iterator it = bindings.iterator(); it.hasNext();) {
1337: final Binding binding = (Binding) it.next();
1338: if (loadedBindings.contains(binding)) {
1339: continue;
1340: }
1341: loadedBindings.add(binding);
1342:
1343: // bug 1775051: a multiple selection Listbox. When onSelect and loadOnSave cause
1344: // setSelectedItem (loading) to be called and cause deselection of other multiple
1345: // selected items. Must skip such case.
1346:
1347: // save binding that cause this loadOnSave, no need to load again.
1348: if (binding == savebinding) {
1349: continue;
1350: }
1351:
1352: Component comp = binding.getComponent();
1353: if (isTemplate(comp)) { //a template component, locate the listitem
1354: Component clonecomp = null;
1355: if (isClone(collectionComp)) { //A listbox in listbox
1356: clonecomp = lookupClone(collectionComp, comp);
1357: } else {
1358: clonecomp = getCollectionItem(comp, bean);
1359: }
1360: if ("_var".equals(binding.getAttr())) {
1361: if (clonecomp == null) { //the comp is in another Listbox
1362: clonecomp = getCollectionItem(comp, bean);
1363: }
1364: collectionComp = clonecomp;
1365: }
1366: comp = clonecomp;
1367: }
1368:
1369: if (refChanged) {
1370: binding.loadAttribute(comp);
1371: }
1372: }
1373: return collectionComp;
1374: }
1375:
1376: private boolean isSameBean(BindingNode node, Object bean) {
1377: final Collection bindings = node.getBindings();
1378: if (bindings.isEmpty()) {
1379: return true;
1380: }
1381: final Component comp = ((Binding) bindings.iterator()
1382: .next()).getComponent();
1383: if (isTemplate(comp)) {
1384: return true;
1385: }
1386: final Object nodebean = getBeanWithExpression(comp, node
1387: .getPath());
1388: return Objects.equals(nodebean, bean);
1389: }
1390: }
1391: }
|