Package org.apache.cocoon.forms.util

Source Code of org.apache.cocoon.forms.util.WidgetFinder$RefreshingRepeaterListener

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.forms.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.cocoon.forms.event.RepeaterEvent;
import org.apache.cocoon.forms.event.RepeaterEventAction;
import org.apache.cocoon.forms.event.RepeaterListener;
import org.apache.cocoon.forms.event.WidgetEventMulticaster;
import org.apache.cocoon.forms.formmodel.Repeater;
import org.apache.cocoon.forms.formmodel.Widget;
import org.apache.commons.lang.StringUtils;

/**
* An utility class to manage list of widgets.
*
* <p>
* The {@link org.apache.cocoon.forms.formmodel.Widget#lookupWidget(String)} method is able
* to only return one widget, while this class returns a list of widgets. It uses a path syntax containing a /./,
* <code>repeater/./foo</code>, which repreesents all the instances of the foo widget inside the repeater,
* one per row. Note that it also supports finding a widgets inside multi level repeaters, something like
* invoices/./movements/./amount or courseYears/./exams/./preparatoryCourses/./title .
* </p>
* <p>
* Class has been designed to offer good performances, since the widget list is built only once and
* is automatically updated when a repeater row is added or removed.
* {@link org.apache.cocoon.forms.event.RepeaterListener}s can be attached directly to receive notifications
* of widget additions or removals.
* </p>
* <p>
* This class is used in {@link org.apache.cocoon.forms.formmodel.CalculatedField}s and
* {@link org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm}s.
* </p>
* @version $Id$
*/
public class WidgetFinder {

    private boolean keepUpdated = false;
  
    // Holds all the widgets not child of a repeater.
    private List noRepeaterWidgets = null;
    // Map repeater -> Set of Strings containing paths
    private Map repeaterPaths = null;
    // Map repeater -> Set of Widgets
    private Map repeaterWidgets = null;
    // A List of recently added widgets, will get cleared when getNewAdditions is called.
    private List newAdditions = new ArrayList();
   
    private RefreshingRepeaterListener refreshingListener = new RefreshingRepeaterListener();
   
    private RepeaterListener listener;
   
    /**
     * Searches for widgets. It will iterate on the given paths and find all
     * corresponding widgets. If a path is in the forms repeater/* /widget
     * then all the rows of the repeater will be iterated and subwidgets
     * will be fetched.
     * @param context The context widget to start from.
     * @param paths An iterator of Strings containing the paths.
     * @param keepUpdated If true, listeners will be installed on repeaters
     * to keep lists updated without polling.
     */
    public WidgetFinder(Widget context, Iterator paths, boolean keepUpdated) {
        this.keepUpdated = keepUpdated;
        while (paths.hasNext()) {
            String path= (String)paths.next();
            path = toAsterisk(path);
            if (path.indexOf('*') == -1) {
                addSimpleWidget(context, path);
            } else {
                recurseRepeaters(context, path, true);
            }
        }
    }
   
    /**
     * Searches for widgets. If path is in the forms repeater/* /widget
     * then all the rows of the repeater will be iterated and subwidgets
     * will be fetched.
     * @param context The context widget to start from.
     * @param path Path to search for..
     * @param keepUpdated If true, listeners will be installed on repeaters
     * to keep lists updated without polling.
     */
    public WidgetFinder(Widget context, String path, boolean keepUpdated) {
        path = toAsterisk(path);
        this.keepUpdated = keepUpdated;
        if (path.indexOf('*') == -1) {
            addSimpleWidget(context, path);
        } else {
            recurseRepeaters(context, path, true);
        }       
    }

    private String toAsterisk(String path) {
        return StringUtils.replace(path, "/./", "/*/");
    }

    /**
     * Recurses a repeater path with asterisk.
     * @param context The context widget.
     * @param path The path.
     */
    private void recurseRepeaters(Widget context, String path, boolean root) {
        String reppath = path.substring(0, path.indexOf('*') - 1);
        String childpath = path.substring(path.indexOf('*') + 2);
        Widget wdg = context.lookupWidget(reppath);
        if (wdg == null) {
            if (root) {
                throw new IllegalArgumentException("Cannot find a repeater with path " + reppath + " relative to widget " + context.getName());
            } else {
                return;
            }
        }
        if (!(wdg instanceof Repeater)) {
            throw new IllegalArgumentException("The widget with path " + reppath + " relative to widget " + context.getName() + " is not a repeater!");
        }
        Repeater repeater = (Repeater)wdg;
        if (context instanceof Repeater.RepeaterRow) {
            // Add this repeater to the repeater widgets
            addRepeaterWidget((Repeater) context.getParent(), repeater);
        }
       
        addRepeaterPath(repeater, childpath);
        if (childpath.indexOf('*') != -1) {
            for (int i = 0; i < repeater.getSize(); i++) {
                Repeater.RepeaterRow row = repeater.getRow(i);
                recurseRepeaters(row, childpath, false);
            }
        } else {
            for (int i = 0; i < repeater.getSize(); i++) {
                Repeater.RepeaterRow row = repeater.getRow(i);
              Widget okwdg = row.lookupWidget(childpath);
              if (okwdg != null) {
                  addRepeaterWidget(repeater, okwdg);
              }
            }
        }
    }
   
    /**
     * Adds to the list a widget descendant of a repeater.
     * @param repeater The repeater.
     * @param okwdg The widget.
     */
    private void addRepeaterWidget(Repeater repeater, Widget okwdg) {
        if (this.repeaterWidgets == null) this.repeaterWidgets = new HashMap();
        Set widgets = (Set) this.repeaterWidgets.get(repeater);
        if (widgets == null) {
            widgets = new HashSet();
            this.repeaterWidgets.put(repeater, widgets);
        }
        widgets.add(okwdg);
        newAdditions.add(okwdg);
    }

    /**
     * Adds a repeater monitored path.
     * @param repeater The repeater.
     * @param childpath The child part of the path.
     */
    private void addRepeaterPath(Repeater repeater, String childpath) {
        if (this.repeaterPaths == null) this.repeaterPaths = new HashMap();
        Set paths = (Set) this.repeaterPaths.get(repeater);
        if (paths == null) {
            paths = new HashSet();
            this.repeaterPaths.put(repeater, paths);
            if (keepUpdated) repeater.addRepeaterListener(refreshingListener);
        }
        paths.add(childpath);
    }

    /**
     * Called when a new row addition event is received from a monitored repeater.
     * @param repeater The repeated that generated the event.
     * @param index The new row index.
     */
    protected void refreshForAdd(Repeater repeater, int index) {
        Repeater.RepeaterRow row = repeater.getRow(index);
        if (this.repeaterPaths == null) this.repeaterPaths = new HashMap();
        Set paths = (Set) this.repeaterPaths.get(repeater);
        for (Iterator iter = paths.iterator(); iter.hasNext();) {
            String path = (String) iter.next();
            if (path.indexOf('*') != -1) {
                recurseRepeaters(row, path, false);
            } else {
                Widget wdg = row.lookupWidget(path);
                if (wdg == null) {
                    throw new IllegalStateException("Even after row addition cannot find a widget with path " + path + " in repeater " + repeater.getName());
                }
                addRepeaterWidget(repeater, wdg);
            }
        }
    }
   
    /**
     * Called when a row deletion event is received from a monitored repeater.
     * @param repeater The repeated that generated the event.
     * @param index The deleted row index.
     */
    protected void refreshForDelete(Repeater repeater, int index) {
        Repeater.RepeaterRow row = repeater.getRow(index);
        Set widgets = (Set) this.repeaterWidgets.get(repeater);
        for (Iterator iter = widgets.iterator(); iter.hasNext();) {
            Widget widget = (Widget) iter.next();
            boolean ischild = false;
            Widget parent = widget.getParent();
            while (parent != null) {
                if (parent == row) {
                    ischild = true;
                    break;
                }
                parent = parent.getParent();
            }
            if (ischild) {
                iter.remove();
                if (widget instanceof Repeater) {
                    if (this.repeaterPaths != null) this.repeaterPaths.remove(widget);
                    this.repeaterWidgets.remove(widget);
                }
            }
        }
    }

    /**
     * Called when a repeater clear event is received from a monitored repeater.
     * @param repeater The repeated that generated the event.
     */ 
    protected void refreshForClear(Repeater repeater) {
        Set widgets = (Set) this.repeaterWidgets.get(repeater);
        for (Iterator iter = widgets.iterator(); iter.hasNext();) {
            Widget widget = (Widget) iter.next();
            if (widget instanceof Repeater) {
                if (this.repeaterPaths != null) this.repeaterPaths.remove(widget);
                this.repeaterWidgets.remove(widget);
            }
        }
        widgets.clear();
    }
   
    /**
     * Adds a widget not contained in a repeater.
     * @param context
     * @param path
     */
    private void addSimpleWidget(Widget context, String path) {
        Widget widget = context.lookupWidget(path);
        if (widget == null) throw new IllegalArgumentException("Cannot find a widget with path " + path + " relative to widget " + context.getName());
        if (this.noRepeaterWidgets == null) this.noRepeaterWidgets = new ArrayList();
        this.noRepeaterWidgets.add(widget);
        newAdditions.add(widget);       
    }
   
    /**
     * Return all widgets found for the given paths.
     * @return A Collection of {@link Widget}s.
     */
    public Collection getWidgets() {
        List list = new ArrayList();
        if (this.noRepeaterWidgets != null) list.addAll(this.noRepeaterWidgets);
        if (this.repeaterWidgets != null) {
          for (Iterator iter = this.repeaterWidgets.keySet().iterator(); iter.hasNext();) {
              Repeater repeater = (Repeater) iter.next();
              list.addAll((Collection)this.repeaterWidgets.get(repeater));
          }
        }
        return list;
    }
   
    /**
     * @return true if this finder is mutable (i.e. it's monitoring some repeaters) or false if getWidgets() will always return the same list (i.e. it's not monitoring any widget).
     */
    public boolean isMutable() {
        return (this.repeaterPaths != null) && this.repeaterPaths.size() > 0;
    }
   
   
    class RefreshingRepeaterListener implements RepeaterListener {
        public void repeaterModified(RepeaterEvent event) {
            if (event.getAction() == RepeaterEventAction.ROW_ADDED) {
                refreshForAdd((Repeater)event.getSourceWidget(), event.getRow());
            }
            if (event.getAction() == RepeaterEventAction.ROW_DELETING) {
                refreshForDelete((Repeater)event.getSourceWidget(), event.getRow());
            }
            if (event.getAction() == RepeaterEventAction.ROWS_CLEARING) {
                refreshForClear((Repeater)event.getSourceWidget());
            }
            if (listener != null) {
                listener.repeaterModified(event);
            }
        }
    }
   
    /**
     * @return true if new widgets have been added to this list (i.e. new repeater rows have been created) since last time getNewAdditions() was called.
     */
    public boolean hasNewAdditions() {
        return this.newAdditions.size() > 0;
    }
   
    /**
     * Gets the new widgets that has been added to the list, as a consequence of new repeater rows additions, since
     * last time this method was called or the finder was initialized.
     * @return A List of {@link Widget}s.
     */
    public List getNewAdditions() {
        List ret = new ArrayList(newAdditions);
        newAdditions.clear();
        return ret;
    }
   
    /**
     * Adds a repeater listener. New widget additions or deletions will be notified thru this listener (events received
     * from monitored repeaters will be forwarded, use {@link #getNewAdditions()} to retrieve new widgets).
     * @param listener The listener to add.
     */
    public void addRepeaterListener(RepeaterListener listener) {
        this.listener = WidgetEventMulticaster.add(this.listener, listener);
    }

    /**
     * Removes a listener. See {@link #addRepeaterListener(RepeaterListener)}.
     * @param listener The listener to remove.
     */
    public void removeRepeaterListener(RepeaterListener listener) {
        this.listener = WidgetEventMulticaster.remove(this.listener, listener);
    }

    /**
     * @return true if there are listeners registered on this instance. See {@link #addRepeaterListener(RepeaterListener)}.
     */
    public boolean hasRepeaterListeners() {
        return this.listener != null;
    }
}
TOP

Related Classes of org.apache.cocoon.forms.util.WidgetFinder$RefreshingRepeaterListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.