001    package net.sf.tacos.components.tree;
002    
003    import java.io.Serializable;
004    import java.util.ArrayList;
005    import java.util.Collection;
006    import java.util.Collections;
007    import java.util.Comparator;
008    import java.util.HashMap;
009    import java.util.List;
010    import java.util.Map;
011    import java.util.Set;
012    import java.util.Stack;
013    import net.sf.tacos.model.IKeyProvider;
014    import net.sf.tacos.model.ITreeContentProvider;
015    import net.sf.tacos.model.IdentityKeyProvider;
016    import org.apache.commons.lang.StringEscapeUtils;
017    import org.apache.commons.logging.Log;
018    import org.apache.commons.logging.LogFactory;
019    import org.apache.tapestry.BaseComponent;
020    import org.apache.tapestry.IActionListener;
021    import org.apache.tapestry.IDirect;
022    import org.apache.tapestry.IMarkupWriter;
023    import org.apache.tapestry.IRequestCycle;
024    import org.apache.tapestry.IScript;
025    import org.apache.tapestry.PageRenderSupport;
026    import org.apache.tapestry.TapestryUtils;
027    import org.apache.tapestry.bean.EvenOdd;
028    import org.apache.tapestry.components.Any;
029    import org.apache.tapestry.engine.DirectServiceParameter;
030    import org.apache.tapestry.engine.IEngineService;
031    import org.apache.tapestry.listener.ListenerInvoker;
032    
033    /**
034     * Base component for providing Tree-like semantics for displaying data.
035     * 
036     * @author phraktle
037     */
038    public abstract class Tree extends BaseComponent implements /*PartialRenderBlock, */IDirect {
039    
040        /** Logger */
041        private static final Log log = LogFactory.getLog(Tree.class);
042        /** Default key provider */
043        private static final IKeyProvider identityKeyProvider = new IdentityKeyProvider();
044        /** Default iterator */
045        private TreeIterator treeIterator;
046        /** Parent nodes */
047        private Stack parts;
048        
049        /**
050         * The default {@link IdentityKeyProvider}.
051         * 
052         * @return The default key provider.
053         */
054        public IKeyProvider getIdentityKeyProvider()
055        {
056            return identityKeyProvider;
057        }
058        
059        /**
060         * The default {@link TreeIterator} provided if no other is specified.
061         * 
062         * @return Instance of {@link TreeIterator}
063         */
064        public TreeIterator getTreeIterator()
065        {
066            return treeIterator;
067        }
068        
069        /**
070         * {@inheritDoc}
071         */
072        protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
073        {
074            boolean dynamic = cycle.getResponseBuilder().isDynamic();
075            //Only if this is a window load and delayed loading is needed
076            if (!cycle.isRewinding() 
077                    && isDelayedLoad()
078                    && !dynamic/*getAjaxWebRequest().isValidRequest()*/) {            
079                renderDynamicLoad(writer, cycle);
080                return;
081            }        
082            treeIterator = new TreeIter();
083            parts = new Stack();
084            try {
085                super.renderComponent(writer, cycle);
086            } finally {
087                try {
088                    while(!parts.isEmpty()) {
089                        String partId = parts.pop().toString();
090                        /*if (getAjaxWebRequest().isValidRequest()
091                                && cycle.getResponseBuilder().componentWriterExists(partId))
092                            getAjaxWebRequest().getResponseBuilder().getComponentWriter(partId).end();
093                        else
094                            writer.end();*/
095                    }
096                } catch (Throwable t) {
097                    t.printStackTrace();
098                }
099                treeIterator = null;
100                parts = null;
101            }
102        }
103        
104        private void renderDynamicLoad(IMarkupWriter writer, IRequestCycle cycle) {
105            //Render our node manually instead of using Any
106            //we still have to have a node to replace on the client
107            Any anyContainer = (Any)getComponent("treeDiv");
108            writer.begin(anyContainer.getElement());
109            super.renderInformalParameters(writer, cycle);
110            writer.end();
111    
112            String treeId = getClientId();
113    
114            /*AjaxDirectServiceParameter dsp = 
115                new AjaxDirectServiceParameter(this, new Object[0], 
116                        new String[] {treeId}, false);*/
117    
118            DirectServiceParameter dsp = new DirectServiceParameter(this);
119    
120            Map parms = new HashMap();
121            parms.put("treeId", treeId);
122            parms.put("url", StringEscapeUtils.escapeJavaScript(
123                            getDirectService().getLink(true, dsp).getAbsoluteURL()));
124            if (getLoadElement() != null)
125                parms.put("loadElement", getLoadElement());
126    
127            PageRenderSupport pageRenderSupport = TapestryUtils.getPageRenderSupport(cycle, this);
128            getScript().execute(this, cycle, pageRenderSupport, parms);        
129        }
130    
131        /**
132         * Called to request the expansion of a tree node.
133         * 
134         * @param cycle
135         */
136        public void expansion(IRequestCycle cycle)
137        {
138            Object[] params = cycle.getListenerParameters();
139            Serializable key = (Serializable)params[0];
140            boolean expanded = ((Boolean)params[1]).booleanValue();        
141            
142            getManager().setExpandedKey(key, expanded);
143            setState(getState());  
144        }
145    
146        /**
147         * Invoked by contentLinkToggle component, will invoke
148         * {@link #expansion(IRequestCycle)} first before checking if the invoking
149         * component specified an {@link IActionListener} and any optional
150         * parameters it may need.
151         * 
152         * @param cycle
153         */
154        public void contentExpansion(IRequestCycle cycle)
155        {
156            // Call expansion first
157            expansion(cycle);
158            
159            // If they specified a listener we will now invoke it
160            if (getLinkListener() == null) {
161                log.warn("contentExpansion() called but no linkListener was specified on the tree. "
162                                + "Use the linkListener parameter if you want your page/component to be invoked "
163                                + "as well.");
164                return;
165            }
166            
167            getListenerInvoker().invokeListener(getLinkListener(), this, cycle);
168        }
169    
170        /**
171         * @param element
172         *            to test expanded of
173         * @return True if element should be displayed/expanded.
174         */
175        public boolean isExpanded(Object element)
176        {
177            return getManager().isExpanded(element);
178        }
179    
180        /*
181         * Default {@link TreeIterator} implementation.
182         */
183        private final class TreeIter extends TreeIterator {
184    
185            /**
186             * Creates new iterator
187             *
188             */
189            private TreeIter()
190            {
191                super(sorted(getContentProvider().getElements()));
192            }
193    
194            /**
195             * 
196             * {@inheritDoc}
197             */
198            protected Collection getChildren(Object node)
199            {
200                if (!isExpanded(node)) { return Collections.EMPTY_LIST; }
201                Collection coll = getContentProvider().getChildren(node);
202                return sorted(coll);
203            }
204            
205        }
206        
207        /**
208         * Default comparator/sorter for tree elements.
209         * 
210         * @param elements
211         * @return
212         */
213        private Collection sorted(Collection elements)
214        {
215            Comparator comp = getSorter();
216            if (comp == null) { return elements; }
217            List l = new ArrayList(elements);
218            Collections.sort(l, comp);
219            return l;
220        }
221    
222        /**
223         * @return The {@link ITreeManager} for this tree.
224         */
225        public ITreeManager getManager()
226        {
227            return new TreeManager(getState(), getContentProvider(),
228                    getKeyProvider());
229        }
230        
231        /**
232         * {@inheritDoc}
233         */
234        public void beforeRenderBody(IMarkupWriter writer, IRequestCycle cycle,
235                Serializable partId, boolean isRendering)
236        {
237            //If we need to close a parent
238            if (treeIterator.getDepth() <= treeIterator.getPreviousDepth()
239                    && !parts.isEmpty()) {
240                
241                int pops = 1;
242                if (treeIterator.getDepth() < treeIterator.getPreviousDepth()) {
243                    pops = treeIterator.getPreviousDepth()
244                            - treeIterator.getDepth();
245                    pops++; // because iterator depth is 0-based
246                }
247                while(pops > 0 && !parts.isEmpty()) {
248                    Serializable id = (Serializable)parts.pop();
249                    if (isRendering) {
250                        
251                        /*if (getAjaxWebRequest().isValidRequest()
252                                && getAjaxWebRequest().containsComponentId(id.toString()))
253                            getAjaxWebRequest().getResponseBuilder().getComponentWriter(id.toString()).end();
254                        else*/ {
255                            IMarkupWriter pwriter = findParentPart();
256                            if (pwriter != null) pwriter.end();
257                            else
258                                writer.end();
259                        }
260                    }
261                    pops--;
262                }
263            }
264            
265            if (isRendering) {
266                writer.begin("div");
267                writer.attribute("id", partId.toString());
268                if (isRowStyleInOuterDiv() && getRowStyle() != null)
269                {
270                    writer.attribute("class", getRowStyle().getNext());
271                }
272                else if (getPartialBlockClass() != null)
273                {
274                    writer.attribute("class", getPartialBlockClass());
275                }
276                parts.push(partId.toString());
277            }
278        }
279        
280        /**
281         * {@inheritDoc}
282         */
283        public void incrementNonRenderBlock()
284        {
285            if (getRowStyle() != null) getRowStyle().getNext();
286            beforeRenderBody(null, null, getKeyProvider().getKey(getValue()), false);
287        }
288        
289        /**
290         * {@inheritDoc}
291         */
292        public IMarkupWriter getPartWriter(Serializable partId)
293        {
294            String partStr = partId.toString();
295            // It may be specified directly
296            IMarkupWriter ret = null;
297            
298            // If we are not a root node we'll try
299            // and find a matching parent
300            if (treeIterator.getDepth() < treeIterator.getPreviousDepth()
301                    && !parts.isEmpty()
302                    && ((parts.size() - 1) > (treeIterator.getPreviousDepth() - treeIterator
303                            .getDepth()))) {
304                ret = findParentPart();
305            } else if (treeIterator.getDepth() > treeIterator.getPreviousDepth()
306                    && !parts.isEmpty()) {
307                ret = findParentPart();
308            } else if (treeIterator.getDepth() == treeIterator.getPreviousDepth()
309                    && parts.size() > 1) {
310                ret = findParentPart();
311            }
312            
313            return ret;
314        }
315        
316        /**
317         * Loops through the current part stack looking for a part that returns a
318         * valid {@link IMarkupWriter}.
319         * 
320         * @return Valid writer, or null.
321         */
322        private IMarkupWriter findParentPart()
323        {
324            //if (!getAjaxWebRequest().isValidRequest())        
325                //return null;
326            
327            IMarkupWriter ret = null;
328            Serializable[] partIds = (Serializable[])parts
329                    .toArray(new Serializable[parts.size()]);
330            /*for(int i = 0; i < partIds.length; i++) {
331                if (getAjaxWebRequest().containsComponentId(partIds[i].toString()))
332                    return getAjaxWebRequest().getResponseBuilder().getComponentWriter(partIds[i].toString());
333            }*/
334            
335            return ret;
336        }
337        
338        /**
339         * Used to pass self into {@link PartialForeach}.
340         * 
341         * @return
342         *//*
343        public PartialRenderBlock getPartialTreeBlock()
344        {
345            return this;
346        }*/
347        
348        public boolean getCurrentHasChildren() {
349            return getContentProvider().hasChildren(getValue());
350        }
351        
352        public Serializable getCurrentKey() {
353            return getKeyProvider().getKey(getValue());
354        }
355        
356        public boolean getCurrentNotExpanded() {
357            return !isExpanded(getValue());
358        }
359        
360        public String getCurrentOffset() {
361            int off = (getContentProvider().hasChildren(getValue())) ? 0 : 1;
362            return "" + ( off + getTreeIterator().getDepth() ) * getOffset();
363        }
364        
365        /**
366         * {@inheritDoc}
367         */
368        public boolean isStateful()
369        {
370            return true;
371        }
372        
373        /**
374         * {@inheritDoc}
375         */
376        public void trigger(IRequestCycle cycle)
377        {
378            //Does nothing, only used in delayed loads
379        }
380    
381        /** Injected script */
382        public abstract IScript getScript();
383        /** Injected ajax engine */
384        public abstract IEngineService getDirectService();
385        /** Current value being rendered */
386        public abstract Object getValue();
387        /** Gets default/specified state */
388        public abstract Set getState();
389        /** Saves tree state */
390        public abstract void setState(Set state);
391        /** Injected content provider */
392        public abstract ITreeContentProvider getContentProvider();
393        /** Injected key provider */
394        public abstract IKeyProvider getKeyProvider();
395        /** Injected sort {@link Comparator} */
396        public abstract Comparator getSorter();
397        /** Optional EvenOdd component */
398        public abstract EvenOdd getRowStyle();
399        /** The offset in pixels for depth-indentation */
400        public abstract int getOffset();     
401        /** Where to apply the row style */
402        public abstract boolean isRowStyleInOuterDiv();    
403        /** Partial block class */
404        public abstract String getPartialBlockClass();
405        /** For invoking linkToggleListener, if it exists */
406        public abstract ListenerInvoker getListenerInvoker();
407        /** Listener to invoke */
408        public abstract IActionListener getLinkListener();
409        /** Delayed loading parameter */
410        public abstract boolean isDelayedLoad();    
411        /** Delayed loading display element id */
412        public abstract String getLoadElement();
413    }