Re: Barracuda: Re:Barracuda Memory troubles

Shawn Wilson <[email protected]>
Newsgroups gmane.comp.java.enhydra.barracuda.general
Organization ATMReports.com
Message-ID <[email protected]>
With Christian's help I went ahead and made the appropriate changes to 
BTemplate and TemplateHelper to fix the memory leak issue. This 
basically involved, as was already suggested, making sure that when 
addModel() is called that the model does not register a duplicate 
interest with the component listener. Also, destroyCycle() was updated 
to make sure that the registered models get disconnected from the 
component listener.

Search the attached files for "saw_121002.1" to see the changes. Let me 
know if you have any questions. Christian mentioned to me that he can 
apply the changes to CVS sometime tomorrow afternoon.

Thanks,
-shawn

Vas Skrypnyk wrote:
> Hi Christian,
> 
> 
>>Hi Bill,
>>
>>So it sounds like you release your handle to the BTemplate, but the Model
>>still has a reference to it, which in turn would keep the BTemplate (and any
>>components it contains) from being 
>>gc'd, since your model still has
>>references to them. Is that correct?
> 
> 
> That's correct. 
> 
> 
>>It almost sounds like when a component calls the destroyCycle() method, it
>>needs a mechanism to disconnect any models which might 
>>be listening to that
>>component. Thoughts?
> 
> 
> That sounds right. If destroyCycle() is called as the finalization code on the BTemplate object then, since it has references to all its models it should be able to disconnect itself. It will still be possible to hook in the same model to the same BTemplate and get the listener connection replicated, but, I suppose, the programmers should take some responsibility for their code :))
> 
> Cheers,
> --Bill.
> 
> 
>>Christian
>>----------------------------------------------
>>Christian Cryder [[email protected]]
>>Internet Architect, ATMReports.com
>>Barracuda - http:
>>//barracuda.enhydra.org
>>----------------------------------------------
>>"Coffee? I could quit anytime, just not today"
>>
>>
>>
>>>-----Original Message-----
>>>From: [email protected] [mailto:barracuda-
>>
>>[email protected]]On
>>
>>>Behalf Of V B Skrypnyk
>>>Sent: Saturday, December 07, 2002 10:17 PM
>>>To: [email protected]
>>>Subject: Barracuda: Re:Barracuda Memory troubles
>>>
>>>
>>>Christian,
>>>
>>>
>>
>>I have found where the memory leak was happening in my code. It
>>
>>>is caused by the following scenario:
>>>
>>>1. Create a BTemplate that will last for one servlet lifecycle
>>>(MainScreen).
>>>2. Create 
>>
>>a Model that represents some persistent data and store
>>
>>>it in the session (Tabs)
>>>3. Have different portions of your application access
>>>session-saved Model and set up their data in it. (Tabs)
>>
>>>4. On every MainScreen instantiation add the model, so that the
>>>respective portion of the template is rendered, i.e. Tabs.
>>>
>>>What happens now is that on every addModel() call a new listener
>>
>>>is added to the model and the list keeps growing until the model
>>>drags around megabytes of listeners, as happened in my case.
>>>
>>>Now, maybe instead of appending the listeners in the List it is
>>
>>>worthwhile checking if this listener has already been added, or
>>>maybe I am misusing the Barracuda framework.
>>>
>>>Please let me know what you think.
>>>
>>>Cheers,
>>>--Bill.
>>>_______________________________________________
>>
>>>Barracuda mailing list
>>>[email protected]
>>>http://www.enhydra.org/mailman/listinfo.cgi/barracuda
>>>FAQ - http://www.jguru.com/faq/Barracuda
>>
>>_______________________________________________
>>
>>Barracuda mailing list
>>[email protected]
>>http://www.enhydra.org/mailman/listinfo.cgi/barracuda
>>FAQ - http://www.jguru.com/faq/Barracuda
>>
> 
> _______________________________________________
> Barracuda mailing list
> [email protected]
> http://www.enhydra.org/mailman/listinfo.cgi/barracuda
> FAQ - http://www.jguru.com/faq/Barracuda

-- 
====================================
Shawn Wilson [[email protected]]
Software Developer, ATMReports.com
PH: 877-327-0873, FAX: 406-294-5806
====================================
BTemplate.java (text/plain, 9.3 KB)
/*
 * Enhydra Java Application Server Project
 * 
 * The contents of this file are subject to the Enhydra Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License on
 * the Enhydra web site (http://www.enhydra.org/).
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 
 * the License for the specific terms governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Enhydra Application Server is Lutris
 * Technologies, Inc. The Enhydra Application Server and portions created
 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
 * All Rights Reserved.
 * 
 * Contributor(s):
 * 
 * $Id: BTemplate.java,v 1.14 2002/12/02 22:21:19 cryd0221 Exp $
 */
package org.enhydra.barracuda.core.comp;

import java.util.*;

import org.apache.log4j.*;
import org.w3c.dom.*;
import org.w3c.dom.html.*;

import org.enhydra.barracuda.core.comp.model.*;
import org.enhydra.barracuda.core.comp.renderer.*;
import org.enhydra.barracuda.core.comp.renderer.html.*;
import org.enhydra.barracuda.core.comp.renderer.xml.*;
import org.enhydra.barracuda.core.util.dom.DOMUtil;
import org.enhydra.barracuda.core.view.*;
import org.enhydra.barracuda.plankton.*;

/**
 * A BTemplate is used to process part of a DOM as a template--the
 * component will look for directives and then query the models to
 * return the data associated with a given key. BTemplate essentially
 * brings "pull-mvc" to XMLC.
 *
 * <p>In the case of BTemplate, you will ALMOST ALWAYS need to manually
 * bind it to a View, unless you happen to be returning it from a model
 * (in which case this will be done for you automatically)
 */
public class BTemplate extends BComponent {

    //public vars
    protected static final Logger logger = Logger.getLogger(BTemplate.class.getName());

    
    //private vars
    protected HashMap templateModels = new HashMap();
    private LocalModelListener callback = new LocalModelListener();
//    protected Node templateNode = null;
//    protected Stack itStack = null;

    /**
     * @clientCardinality 1..* 
     */
    private TemplateModel lnkTemplateModel;


    //--------------- Constructors -------------------------------
    /**
     * Public noargs constructor
     */
    public BTemplate() {
        this (null, null);
    }
    
    /**
     * Public constructor which creates the component and
     * binds it to a specific model. You will need to manually
     * set the View if you use this constructor.
     *
     * @param model the specific model to back this component
     */
    public BTemplate(TemplateModel imodel) {
        this (imodel, null);
    }

    /**
     * Public constructor which creates the component and
     * binds it to a specific model. The component is also
     * bound to the specified view. 
     * 
     * <p>Null values may be passed in for any parameters, 
     * but if you do so you will need manually provide these
     * values (via the accessor methods) prior to actually 
     * rendering the component
     *
     * @param model the specific model to back this component
     * @param view the View the component should be bound to
     */
    BTemplate(TemplateModel imodel, TemplateView iview) {
        if (imodel!=null) addModel(imodel);
        if (iview!=null) this.addView(iview);    
    }
    



    //--------------- Renderer -----------------------------------
    /**
     * Default component renderer factory registrations
     */
    static {
        HTMLRendererFactory rfHTML = new HTMLRendererFactory();
        installRendererFactory(rfHTML, BTemplate.class, HTMLElement.class);
        installRendererFactory(rfHTML, BTemplate.class, HTMLDocument.class);

        XMLRendererFactory rfXML = new XMLRendererFactory();
        installRendererFactory(rfXML, BTemplate.class, Node.class);
    }

    /**
     * HTML RendererFactory
     */
    static class HTMLRendererFactory implements RendererFactory {
        public Renderer getInstance() {return new HTMLTemplateRenderer();}
    }

    /**
     * XML RendererFactory
     */
    static class XMLRendererFactory implements RendererFactory {
        public Renderer getInstance() {return new XMLTemplateRenderer();}
    }



    //--------------- BTemplate ---------------------------
    /**
     * Add a model to the component. Unlike other components, the template
     * component can have ant number of models...
     *
     * @param model a model that backs the template
     */
    public void addModel(TemplateModel imodel) {
        //eliminate the obvious
        if (imodel==null) return;
    
        //saw_121102.1 - deregister if possible
        imodel.removeModelListener(callback);
        
        //add the model    and set the default if necess.
        templateModels.put(imodel.getName(), imodel);
        invalidate();
        
        //reregister if possible
        imodel.addModelListener(callback);
    }
    
    /**
     * Add a whole list of models to the component. 
     *
     * @param list a list of TemplateModels to back the component
     */
    public void addModels(List ilist) {
        //eliminate the obvious
        if (ilist==null) return;
    
        //iterate through the list, adding any instances of TemplateModel
        Iterator it = ilist.iterator();
        while (it.hasNext()) {
            Object o = it.next();
            if (o instanceof TemplateModel) addModel((TemplateModel) o);
        }
    }
    
    /**
     * Remove a model from the component. 
     *
     * @param model a model that backs the template
     */
    public void removeModel(TemplateModel imodel) {
        //eliminate the obvious
        if (imodel==null) return;
    
        //remove the model    and set the default if necess.
        templateModels.remove(imodel.getName());
        invalidate();
        
        //deregister if possible
        imodel.removeModelListener(callback);
    }

    /**
     * Remove a model from the component by model name
     *
     * @param name the name of the model to be removed
     */
    public void removeModel(String modelName) {
        removeModel(getModel(modelName));
    }
    
    //saw_121002.1 - added
    /**
     * Remove all models from the component
     */
    public void removeAllModels() {
        Iterator it = templateModels.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            TemplateModel model = (TemplateModel)entry.getValue();
            
            //remove the model
            it.remove();

            //deregister if possible
            model.removeModelListener(callback);
        }
        
        invalidate();
    }

    /**
     * Get the model that backs the table
     *
     * @param name the name of the model we're interested in
     * @return the model that backs the table
     */
    public TemplateModel getModel(String modelName) {
        return (TemplateModel) templateModels.get(modelName);    
    }
    
    /**
     * Get a list of models associated with this view
     *
     * @return a list of models associated with this view
     */
    public List getModels() {
        return new ArrayList(templateModels.values());    
    }
    
    /**
     * Render a specific view for the component. 
     *
     * @param view View to be rendered
     * @param vc ViewContext for the client view
     * @throws RenderException if the particular View is not supported
     * @param list a List of all the views for this component
     */
/*
//021102.3_csc - removed, because now its in BComponent     
    protected void renderView (View view, ViewContext vc, int depth) throws RenderException {
        if (logger.isInfoEnabled()) logger.info("rendering view: "+view);

        //actually render the view according to known interfaces
        try {
            Renderer r = getRenderer(view);
            r.renderComponent(this, view, vc);
        } catch (DOMException e) {
            logger.warn("DOM Error:", e);
            throw new DOMAccessException("Error rendering component in view:"+e, e);
        }
    }
*/



    //--------------- Lifecycle ----------------------------------
    /**
     * Destroy cycle. The component should use this cycle to
     * perform any special cleanup.
     */
    public void destroyCycle() {
        //default destroy
        super.destroyCycle();
    
        //we set the model to null so that the component can be 
        //garbage collected. If we don't do this, the model retains
        //a reference back to the component and so the component 
        //will never be freed up...
        removeAllModels(); //saw_121002.1 - we should remove all models first so they lose insterest in us
        templateModels = null;   
        callback = null;    //csc_041202.1 - seems like this should be getting cleared too...     
    }




    //--------------- Utility ------------------------------------
/*
    class LocalTemplateModelListener implements TemplateModelListener {
        //get notified when one of the underlying models changes
        public void modelChanged(TemplateModelEvent e) {
            invalidate();
        }
    }
*/    
    class LocalModelListener implements ModelListener {
        //get notified when one of the underlying models changes
        public void modelChanged(Model m) {
            invalidate();
        }
    }
}
TemplateHelper.java (text/plain, 34 KB)
/**
 * Enhydra Java Application Server Project
 * 
 * The contents of this file are subject to the Enhydra Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License on
 * the Enhydra web site (http://www.enhydra.org/).
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 
 * the License for the specific terms governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Enhydra Application Server is Lutris
 * Technologies, Inc. The Enhydra Application Server and portions created
 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
 * All Rights Reserved.
 * 
 * Contributor(s):
 * 
 * $Id: TemplateHelper.java,v 1.31 2002/12/02 22:21:19 cryd0221 Exp $
 */
package org.enhydra.barracuda.core.comp.renderer;

import java.util.*;
import org.w3c.dom.*;
//import org.w3c.dom.html.*;

import org.apache.log4j.*;

import org.enhydra.barracuda.plankton.*;
import org.enhydra.barracuda.core.comp.*;
import org.enhydra.barracuda.core.util.dom.*;
import org.enhydra.barracuda.core.view.*;

/**
 * Provide a default encapsulation of the template rendering stuff.
 */
public class TemplateHelper {

    protected static final Logger logger = Logger.getLogger(TemplateHelper.class.getName());

    final String s = "";

    protected Renderer masterRenderer = null;
    protected BTemplate btemplate = null;
    protected Stack itStack = null;

    static boolean showDebug = false;

    public TemplateHelper(Renderer imasterRenderer) {
        masterRenderer = imasterRenderer;
    }

    /**
     * This renders a BTemplate component into the specified TemplateView,
     * for the given ViewCOntext
     */
    public void render(BTemplate comp, TemplateView view, ViewContext vc) throws RenderException {
        //csc_091401.1_start - the purpose of this mod is to make it possible to process
        //directives in the root node of the template view. We can only do this for
        //BTemplates that are step children because in this case they will only be rendered
        //once and thus its not a big deal to go replacing the root node
/*        
        //start by removing all the children from the target node
        btemplate = (BTemplate) comp;
        Node node = view.getNode();
        while (node.hasChildNodes()) {
            node.removeChild(node.getFirstChild());
        }
        
        //now we're going to iterate through the master template and
        //add children back in, applying template directives as we go
        Node masterTemplate = view.getMasterTemplate();
        copyChildNodes(masterTemplate, node, view, vc, 0);
*/        
        //get a reference to the template and the root node
        btemplate = (BTemplate) comp;
        Node node = view.getNode();
        Node origNode = node;
        Node origParent = origNode.getParentNode();
        Node newNode = null;
    
        //if we're dealing with a step child, we know that this component/view will only be 
        //rendered once. As such, we can actually replace the node that the component is 
        //bound to with one that's been parsed for directives (allowing us to effectively
        //parse the root node for directives as well). We can't do this with permanent views
        //since the component has established a view on the node so we can't just go changing 
        //that node
        if (comp.isStepChild()) {
//System.out.println ("processing stepchild @"+Integer.toHexString(comp.hashCode()));
            newNode = getNode(node, view, vc, 0);
            if (newNode!=null) node = newNode;
        }
        
        //now, if we're NOT dealing with a step child, go ahead and do this. If
        //we ARE dealing with a step child, we want to skip this step (or else it'll
        //end up processing parts of the template twice)
        if (!comp.isStepChild()) {  //csc_032202.1 - added

            //start by removing all the children from the target node
            while (node.hasChildNodes()) {
                node.removeChild(node.getFirstChild());
            }
            
            //now iterate through the master template and add children back in, 
            //applying template directives as we go.
            Node masterTemplate = view.getMasterTemplate();
            copyChildNodes(masterTemplate, node, view, vc, 0);
        }
        
        //now, if we actually are using newNode, then we need to replace the 
        //origNode with it once we're done
        if (newNode!=null) {
//System.out.println ("or:"+origNode+" p1:"+origParent+" p2:"+origNode.getParentNode()+" nn:"+newNode);
            origNode.getParentNode().replaceChild(newNode, origNode);
        }
        //csc_091401.1_end
    }

    /**
     * Copy nodes from the template to the target
     */
    protected void copyChildNodes(Node templateNode, Node targetNode, TemplateView view, ViewContext vc, int depth) throws RenderException {

        //iterate through all the template node's children, copying them across
        Node child = templateNode.getFirstChild();
        Node newChild = null;
        while (child!=null) {                
            //get the new child
            newChild = getNode(child, view, vc, depth+1);
            
            //if we're iterating and we need to loop, reset to the starting
            //point for the current iteration loop
            if (itStack!=null) {
                IteratorContext itcontext = (IteratorContext) itStack.peek();
                if (itcontext.NEXTLOOP) {
                    child = itcontext.startNode;
                    itcontext.NEXTLOOP = false;
                    continue;
                } else if (itcontext.EOF) {
                    //Only set it equal to the endNode if the endNode is not null; 
                    //otherwise, set it equal to the next sibling.
                    if (itcontext.endNode!=null) child = itcontext.endNode;
                    else child = child.getNextSibling();
                    continue;
                }
            }            
            
            //add the newchild in
            if (newChild!=null) {
//if (newChild instanceof Element) System.out.println("Adding newChild:"+newChild+"("+((Element) newChild).getAttribute("name")+")"+" to parent");
//else System.out.println("Adding newChild:"+newChild+" to parent");
                masterRenderer.addChildToParent(targetNode, newChild);
            }
    
            //get the next child
            child = child.getNextSibling();
        }
    }

    /**
     * Given the specified templateNode, return the new Node to be rendered into the view
     */
    protected Node getNode(Node templateNode, TemplateView view, ViewContext vc, int depth) throws RenderException {
        //put the template node into the view context
        vc.putState(ViewContext.TEMPLATE_NODE, templateNode);


        //now start by getting the directives for the current node. 
        //
        //When we look for directives, we do so by looking first to see if the 
        //previous node was a processing instruction that contained directives. 
        //This is how you would use directives in XML, since the DTD probably 
        //wouldn't support them in the class attribute. 
        //
        //Now, once we have checked there, see if the view contains any directive 
        //lists associated witha  particular node. If you are a developer who
        //doesn't want directives embedded in *ML, or if the *ML won't allow for it,
        //this is how you provide for them--by passing in a map to the view which
        //can be used to xref ids to dir lists.
        //
        //finally, look to see if there are any directives directly associated
        //with the class attribute in the *ML. If there are, add them to the 
        //list. 
        //
        //When we're all done, we will have a list consisting of all the directives
        //associated witha  given node...
        if (logger.isDebugEnabled()) logger.debug("Checking for directives");
        List dirList = null;

        //...now look in the attributes
        String origClassAttr = null;
        String newClassAttr = null;
        Map attrMap = new HashMap();
        boolean idMatchesDirectives = false;
        if (templateNode instanceof Element) {
            Element el = (Element) templateNode;

            //...look for directives based on id attribute
            if (logger.isDebugEnabled()) logger.debug("Looking for directives based on id attribute");
            String idName = el.getAttribute(view.getIDAttrName());
            if (idName!=null && idName.trim().length()>0 && view.getDirIDMap()!=null) {
                if (dirList==null) dirList = new ArrayList();
                List tlist = view.lookupDirsByID(idName);
                if (tlist!=null) dirList.addAll(tlist);
                if (dirList.size()>0) idMatchesDirectives = true;
            }

            //...look for directives in the class attribute
            if (logger.isDebugEnabled()) logger.debug("Looking for directives based on class attribute");
            origClassAttr = el.getAttribute(view.getDirAttrName());
            if (origClassAttr!=null) {
                StringBuffer sbNewClassAttr = new StringBuffer(origClassAttr.length());
                StringTokenizer st = new StringTokenizer(origClassAttr, " ");
                while (st.hasMoreTokens()) {
                    String s = st.nextToken();
                    TemplateDirective td = view.lookupDir(s);
                    if (td!=null) {
                        if (dirList==null) dirList = new ArrayList(1);
                        dirList.add(td);
                    } else {
                        sbNewClassAttr.append(s+" ");
                    }
                }
                newClassAttr = sbNewClassAttr.toString().trim();
            }
        }


        //next, process any directives
        Node newNode = null;
        boolean processedDir = false;
        if (dirList!=null && dirList.size()>0) {

            //csc_050702.1 - first and foremost, see if we are in the middle of an iteration. If so,
            //see if we need to SKIP_TO_NEXT. If so, run through the list of directives and strip out 
            //any that occur up to and including the Iterate_Start directive. Then clear the SKIP_TO_NEXT
            //flag in the iterator context. What this should effectively do is allow us to nest start
            //iterator directives within the same node
            if (itStack!=null) {
                IteratorContext itcontext = (IteratorContext) itStack.peek();
                if (itcontext.SKIP_TO_NEXT) {
                    Iterator itDir = dirList.iterator();
                    while (itDir.hasNext()) {
                        TemplateDirective td = (TemplateDirective) itDir.next();
                        if (td.equals(itcontext.startTd)) {
                            itDir.remove();
                            break;
                        } else {
                            itDir.remove();
                        }
                    }                
                    itcontext.SKIP_TO_NEXT = false;
                }
            }

            //iterate through all directives.
            if (logger.isDebugEnabled()) logger.debug("Processing directives");
//System.out.println("Processing directive list");
            Iterator itDir = dirList.iterator();
            TemplateModel tm = null;
            while (itDir.hasNext()) try {
                TemplateDirective td = (TemplateDirective) itDir.next();

                String cmd = td.getCommand();
                String model = td.getModelName();
                String key = td.getKeyName();
                String data = td.getKeyData();
                tm = btemplate.getModel(model);

                Object item = null;
                if (logger.isDebugEnabled()) logger.debug("Next dir-->"+td);

                // If tm is null, that means we couldn't find a TemplateModel by the
                // requested name.  In that case, we'll just log a warning and skip
                // this directive. - Submitted by Jeff French 7/17/2001
                if (tm==null && !cmd.equals(TemplateDirective.DISCARD)) {
                    logger.warn("Cannot find a model named "+model+". Skipping directive.");
                    continue;
                }
                
                //if the directive is aimed at a specific model, let the model 
                //know about it. If this directive gets vetoed, no further 
                //directives will get processed and the entire node will get 
                //skipped
                if (tm!=null) {
                    if (logger.isDebugEnabled()) logger.debug("Notifying model "+model+" of directive");
                    tm.setViewContext(vc);
                    boolean ok = tm.processDirective(td);
                    if (!ok) return null;
                }

                //Dir::Get_Data                
                if (cmd.equals(TemplateDirective.GET_DATA)) {
                    if (logger.isDebugEnabled()) logger.debug("GET_DATA");

                    //make sure that if we're in the middle of an iteration we still have data
                    //available. If not, don't return this node!
                    if (itStack!=null) {
                        IteratorContext itcontext = (IteratorContext) itStack.peek();
                        if (itcontext.EOF) return null;
                    }

                    //get the item and process accordingly
                    item = tm.getItem(key);                
                    if (item!=null) {
                        //..BComponent
                        if (item instanceof BComponent) {
                            //add in the Node for the first view
                            if (logger.isDebugEnabled()) logger.debug("Getting BComponent item: "+item+"...");
                            BComponent bcomp = (BComponent) item;

                            //this is not really the right way to do this, but its a 
                            //quick fix that will work for now. See csc for details...
                            boolean defaultNodeCreatedViews = false;    //csc_110501.1
                            
                            //if the component needs a default view create one for it...
                            if (!bcomp.hasViews() || bcomp.getViews().size()<1) {  //jrk_040702.2
                                //get the format type and the doc from the view context
                                FormatType ft = vc.getViewCapabilities().getFormatType();
                                Document doc = vc.getElementFactory().getDocument();

                                try {
                                    //get the appropriate renderer by looking up the
                                    //DOM class associated with the given format type
                                    Renderer r = bcomp.getRenderer(ft.getDOMClass());

                                    //ask the renderer to create the default Node
                                    newNode = r.createDefaultNode(doc, bcomp, vc); //csc_110501.1
                                    
                                    //if the component still needs a default view create one for it...
                                    if (bcomp.hasViews()) defaultNodeCreatedViews = true;
                                    else bcomp.addTempView(new DefaultView(newNode));

                                    //finally, invalidate the component to ensure redraw
                                    bcomp.invalidate();
                                    
                                } catch (RenderException e) {
                                    logger.warn("Unable to create default view:", e);
                                } catch (DOMException e) {
                                    logger.warn("Unable to create default view:", e);
                                }
                                
                            //if the component being returned is already bound to views,
                            //just add in the nodes that back the views
                            } else {                                    //csc_110501.1
                                //hmmm...I'm not sure if it would make sense to return components from a template model
                                //that had more than one view. Probably not; in case someone tries it, it won't work
                                //since we only handle the first one here...                            
                                newNode = ((View) bcomp.getViews().get(0)).getNode();
                            }

                            //now add the comp as a temporary child (by doing this, we ensure that
                            //the child will get rendered as well. After rendering, we'll remove
                            //the child from the hierarchy, so as to return to our original state)
                            //
                            //(Note that we DON'T have these step children inherit the parent 
                            //settings...this is because the model will be returning distinct 
                            //components whose visibility/enabled status should NOT be the same as 
                            //that of the master template)
                            btemplate.addStepChild(bcomp);

                            //csc_091301.2_start - added
                            //if the component returned is NOT an instance of BTemplate,
                            //BTable, or BList, then check to see if it has any non-text
                            //children. If so, automatically create BTemplate components
                            //to parse them as well (thereby catching any nested directives
                            //they might also contain...)
                            if (!(bcomp instanceof BTemplate) &&
                                !(bcomp instanceof BTable) &&
                                !(bcomp instanceof BList) &&
                                !(defaultNodeCreatedViews) &&           //csc_110501.1
                                 (newNode!=null && newNode.hasChildNodes())) {

//csc_112102.1_start
//ok, so the problem with this approach is that while immediately rendering the
//btChild here does solved the nested iterative directive problem (making sure they all
//get called in the current iteration, rather than later as was happening when we were
//simply adding to btemplate as a step child), it inadvertantly SKIPS directives that
//are in the DOM layer that is immediately below newNode
/*
                                Node chNode = newNode.getFirstChild();
                                while (chNode!=null) {
                                    if (chNode instanceof Element) {
                                        TemplateView tv = (TemplateView) view.clone();
                                        tv.setNode(chNode);
                                        BTemplate btChild = new BTemplate();
                                        btChild.setView(tv);
                                        btChild.addModels(btemplate.getModels());
//                                        btemplate.addStepChild(btChild);    //csc_111902.1 - roll back changes that got added in csc_100802.1 - these changes where not processing nested directives correctly (the test case was failing too...not sure how I missed that originally)
                                        btChild.render(vc);
                                    }                                 
                                    chNode = chNode.getNextSibling();
                                }
*/
//The solution then, is to run through all the child elements; if any of them
//actually implement element, then create one BTemplate and bind it to newNode
//(rather than each individual child node as we were doing before), and THEN
//render immediately. Voila! All is well...whew! This was a bugger to find.
                                Node chNode = newNode.getFirstChild();
                                boolean hasElements = false;
                                while (chNode!=null) {
                                    if (chNode instanceof Element) {
                                        hasElements = true;
                                        break;
                                    }                                 
                                    chNode = chNode.getNextSibling();
                                }

                                if (hasElements) {
                                    TemplateView tv = (TemplateView) view.clone();
                                    tv.setNode(newNode);
                                    BTemplate broot = new BTemplate();
                                    broot.setView(tv);
                                    broot.addModels(btemplate.getModels());
                                    broot.render(vc);
//Q: Do we need to be calling detroyCycle() here??????                                    
                                    broot.destroyCycle();  //saw_121102.1 - yes, we do
                                }
//csc_112102.1_end
                            }
                            //csc_091301.2_end

                        //..Nodes
                        } else if (item instanceof Node) {
                            if (logger.isDebugEnabled()) logger.debug("Getting Node item: "+item+"...");
                            newNode = (Node) item;


//csc_112102.1_start - same explanation as above
/*
                            //csc_091301.2_start - added
                            //when we get a node back, automatically create a BTemplate
                            //component to parse it as well (using the same models as the 
                            //current template component)
                            TemplateView tv = (TemplateView) view.clone();
                            tv.setNode(newNode);
                            BTemplate btChild = new BTemplate();
                            btChild.setView(tv);
                            btChild.addModels(btemplate.getModels());
//                            btemplate.addStepChild(btChild);    //csc_111902.1 - roll back changes that got added in csc_100802.1 - these changes where not processing nested directives correctly (the test case was failing too...not sure how I missed that originally)
                            btChild.render(vc);
                            //csc_091301.2_end
*/
                            Node chNode = newNode.getFirstChild();
                            boolean hasElements = false;
                            while (chNode!=null) {
                                if (chNode instanceof Element) {
                                    hasElements = true;
                                    break;
                                }                                 
                                chNode = chNode.getNextSibling();
                            }

                            if (hasElements) {
                                TemplateView tv = (TemplateView) view.clone();
                                tv.setNode(newNode);
                                BTemplate broot = new BTemplate();
                                broot.setView(tv);
                                broot.addModels(btemplate.getModels());
                                broot.render(vc);
//Q: Do we need to be calling detroyCycle() here??????                                    
                                broot.destroyCycle();  //saw_121102.1 - yes, we do
                            }
//csc_112102.1_end
                        
                        //..Strings
                        } else {
                            if (logger.isDebugEnabled()) logger.debug("Getting String item: "+item+"...");
                            newNode = templateNode.cloneNode(true);
                            String s = item.toString();
                            BText textComp = new BText(s);
                            textComp.setView(new DefaultView(newNode));
                            btemplate.addStepChild(textComp);
                            
                            //TODO: if the String value contains any directives, create a 
                            //BTemplate component to process them as well...this is 
                            //actually not at all trivial, because by this point the dom 
                            //structure (if there even was one) has been flattened...even
                            //if we can tell there's a directive embedded in the text, its
                            //not clear to me how we'd process it, since the structure is 
                            //flat. We could conceivably try break the string up and reconstruct 
                            //DOM pieces, but I suspect this would be very difficult to do
                            //with any reliability...hmm. More thought needed before we bite 
                            //this one off to do.
                            
                        }
                    //..skip this directive
                    } else { 
                        //csc_091301.1_start - change the meaning of a null item to 
                        //indicate that the node should be skipped
/*
                        if (logger.isDebugEnabled()) logger.debug("Undefined key.");
                        newNode = templateNode.cloneNode(true);
                        String s = model+"."+key+" "+AbstractTemplateModel.UNDEFINED;                    
                        BText textComp = new BText(s, new DefaultView(newNode));
                        btemplate.addStepChild(textComp);
*/                        
                        if (logger.isDebugEnabled()) logger.debug("Null value returned; skipping this directive");
                        return null;
                        //csc_091301.1_end
                    }
                    processedDir = true;
                    
                //Dir::Set_Attr        
                } else if (cmd.equals(TemplateDirective.SET_ATTR)) {
                    if (logger.isDebugEnabled()) logger.debug("SET_ATTR");
                
                    //make sure that if we're in the middle of an iteration we still have data
                    //available. If not, don't return this node!
                    if (itStack!=null) {
                        IteratorContext itcontext = (IteratorContext) itStack.peek();
                        if (itcontext.EOF) return null;
                    }

                    //get the item, convert it to a String and add it to the attr map
                    item = tm.getItem(key);
                    if (item!=null) attrMap.put(data, item.toString());
                    else attrMap.put(data, null);

                //Dir::Discard
                } else if (cmd.equals(TemplateDirective.DISCARD)) {
                    if (logger.isDebugEnabled()) logger.debug("DISCARD");
                    
                    //cause the node to be discarded by returning null immediately
                    return null;
                
                //Dir::Iterate_Start            
                } else if (cmd.equals(TemplateDirective.ITERATE_START)) {
                    if (logger.isDebugEnabled()) logger.debug("ITERATE_START");
                
                    //make sure the model supports iteration
                    if (tm==null || !(tm instanceof IterativeModel)) continue;                
                    IterativeModel itm = (IterativeModel) tm;
                    
                    //create the iterator stack if necessary
                    if (itStack==null) itStack = new Stack();
                    
                    //see if the IteratorContext on the stack has the same starting node
                    //as this one...if so, we've already processed this directive so just 
                    //continue
                    IteratorContext itcontext = null;
                    if (!itStack.empty()) itcontext = (IteratorContext) itStack.peek();
//csc_050702.1                    if (itcontext!=null && itcontext.startNode==templateNode) continue;
                    if (itcontext!=null && itcontext.startNode==templateNode && itcontext.startTd.equals(td)) continue;  //csc_050702.1
                
                    //if not, this is a brand new iteration. Create an iteration 
                    //context and add it to the stack
//csc_050702.1                    itcontext = new IteratorContext(itm, templateNode);
                    itcontext = new IteratorContext(itm, templateNode, td);     //csc_050702.1
                    itStack.push(itcontext);                
                    
                    //notify the IterativeModel to prepare for iteration
                    itm.preIterate();
                
                //Dir::Iterate_Next
                } else if (cmd.equals(TemplateDirective.ITERATE_NEXT)) {
                    if (logger.isDebugEnabled()) logger.debug("ITERATE_NEXT");
                
                    //if itStack is null something is wrong...just continue
                    if (itStack==null) continue;

                    //peek on the stack and get the iteration context. If there is no context,
                    //something's wrong so just continue
                    IteratorContext itcontext = (IteratorContext) itStack.peek();
                    if (itcontext==null) continue;
                    
                    //move the iterator forward. If we're out of records, return immediately
                    if (itcontext.itm.hasNext()) itcontext.itm.loadNext();
                    else { 
                        itcontext.EOF = true;
                        return null;
                    }
                
                //Dir::Iterate_End                
                } else if (cmd.equals(TemplateDirective.ITERATE_END)) {
                    if (logger.isDebugEnabled()) logger.debug("ITERATE_END");
            
                    //if the stack is null something is wrong...just continue
                    if (itStack==null || itStack.empty()) continue;
            
                    //pop the current iteration context back off and give the model
                    //a chance to clean up after iteration
                    IteratorContext itcontext = (IteratorContext) itStack.peek();
                    
                    //if we hit and end directive that is not for the current context,
                    //just ignore it
                    if (!itcontext.startTd.getModelName().equals(model)) continue;      //csc_050702.1

                    //see if we're at the end of the iteration
                    if (itcontext.EOF) {                                        
                        itcontext.itm.postIterate();
                        itStack.pop();
                        if (itStack.empty()) itStack = null;
                        
                    //otherwise, just nextloop    
                    } else {
                        itcontext.NEXTLOOP = true;
                        itcontext.SKIP_TO_NEXT = true;
                        itcontext.endNode = templateNode;
                        
                        //return immediately since we don't want to add this node
                        //in, instead we want to loop back up to the top of the iterator
                        return null;
                    }
                }
            } finally {
                if (tm!=null) tm.setViewContext(null);
            }
        } 
        
        if (!processedDir) {
            //if there aren't any, shallow copy and continue and then 
            //copy in child nodes
            if (logger.isDebugEnabled()) logger.debug("Default Processing");
            newNode = templateNode.cloneNode(false);
            copyChildNodes(templateNode, newNode, view, vc, depth+1);        
        }        
        
        //adjust the outgoing class attributes on the node        
        if (origClassAttr!=null && !(origClassAttr.equals(newClassAttr))) {
            if (logger.isDebugEnabled()) logger.debug("Adjusting class attributes on new node");
            Element elNew = (Element) newNode;
            if (newClassAttr!=null && newClassAttr.length()>0) elNew.setAttribute(view.getDirAttrName(), newClassAttr); 
            else elNew.removeAttribute(view.getDirAttrName());
        }

        //adjust the outgoing id attributes on the node        
        if (idMatchesDirectives) {
            if (logger.isDebugEnabled()) logger.debug("Adjusting id attributes on new node");
            Element elNew = (Element) newNode;
            elNew.removeAttribute(view.getIDAttrName());
        }

        //set any attributes that were set through directives
        if (attrMap.size()>0) {
            if (logger.isDebugEnabled()) logger.debug("Setting attributes resulting from directives");
            Iterator it = attrMap.keySet().iterator();
            Element elNew = (Element) newNode;
            while (it.hasNext()) {
                String key = (String) it.next();
                String attr = (String) attrMap.get(key);
                if (logger.isDebugEnabled()) logger.debug("attr: "+key+"="+attr);
                //don't just passively set non-null values; also actively remove 
                //null values.
                if (attr!=null) elNew.setAttribute(key, attr);
                else elNew.removeAttribute(key);
            }
        }
        
        return newNode;
    }
    


    class IteratorContext {
    
        IterativeModel itm = null;
        Node startNode = null;
        Node endNode = null;
        TemplateDirective startTd = null;   //csc_050702.1
        boolean SKIP_TO_NEXT = false;
        boolean NEXTLOOP = false;
        boolean EOF = false;
    
//csc_050702.1        public IteratorContext(IterativeModel iitm, Node istartNode) {
        public IteratorContext(IterativeModel iitm, Node istartNode, TemplateDirective istartTd) {  //csc_050702.1
            itm = iitm;
            startNode = istartNode;
            endNode = null;
            startTd = istartTd;     //csc_050702.1
//csc_050702.1            EOF = false;
//csc_050702.1            NEXTLOOP = false;
        }
    }
    
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.