You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ja...@apache.org on 2010/05/04 22:06:31 UTC

svn commit: r941020 - in /myfaces/core/trunk: api/src/main/java/javax/faces/component/ impl/src/main/java/org/apache/myfaces/config/ impl/src/main/java/org/apache/myfaces/renderkit/ impl/src/main/java/org/apache/myfaces/view/facelets/component/ impl/sr...

Author: jakobk
Date: Tue May  4 20:06:31 2010
New Revision: 941020

URL: http://svn.apache.org/viewvc?rev=941020&view=rev
Log:
MYFACES-2676 Include the whole component tree with all real values in the debug page (first draft of this new feature)

Added:
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java   (with props)
Modified:
    myfaces/core/trunk/api/src/main/java/javax/faces/component/UIInput.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/FacesConfigurator.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/renderkit/ErrorPageWriter.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/component/UIRepeat.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/webapp/AbstractFacesInitializer.java
    myfaces/core/trunk/impl/src/main/resources/META-INF/rsc/myfaces-dev-debug.xml

Modified: myfaces/core/trunk/api/src/main/java/javax/faces/component/UIInput.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/api/src/main/java/javax/faces/component/UIInput.java?rev=941020&r1=941019&r2=941020&view=diff
==============================================================================
--- myfaces/core/trunk/api/src/main/java/javax/faces/component/UIInput.java (original)
+++ myfaces/core/trunk/api/src/main/java/javax/faces/component/UIInput.java Tue May  4 20:06:31 2010
@@ -19,12 +19,17 @@
 package javax.faces.component;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import javax.el.ELContext;
 import javax.el.ValueExpression;
 import javax.faces.FacesException;
 import javax.faces.application.FacesMessage;
+import javax.faces.application.ProjectStage;
 import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
 import javax.faces.convert.Converter;
@@ -42,6 +47,7 @@ import javax.faces.event.ValueChangeEven
 import javax.faces.event.ValueChangeListener;
 import javax.faces.render.Renderer;
 import javax.faces.validator.Validator;
+import javax.faces.webapp.FacesServlet;
 
 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFListener;
@@ -74,9 +80,16 @@ public class UIInput extends UIOutput im
     
     @JSFWebConfigParam(defaultValue="auto", expectedValues="auto, true, false", since="2.0")
     public static final String VALIDATE_EMPTY_FIELDS_PARAM_NAME = "javax.faces.VALIDATE_EMPTY_FIELDS";
+    
+    /**
+     * Extended debug info is stored under this key in the request
+     * map for every UIInput component when in Development mode.
+     * ATTENTION: this constant is duplicate in org.apache.myfaces.renderkit.ErrorPageWriter
+     */
+    private static final String DEBUG_INFO_KEY = "org.apache.myfaces.debug.DEBUG_INFO";
 
     private static final Validator[] EMPTY_VALIDATOR_ARRAY = new Validator[0];
-
+    
     private _DeltaList<Validator> _validatorList;
 
     /**
@@ -102,6 +115,12 @@ public class UIInput extends UIOutput im
     @Override
     public void setValue(Object value)
     {
+        if (FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development))
+        {
+            // extended debug-info when in Development mode
+            _createFieldDebugInfo(FacesContext.getCurrentInstance(), "localValue",
+                    getLocalValue(), value, 1);
+        }
         setLocalValueSet(true);
         super.setValue(value);
     }
@@ -537,6 +556,21 @@ public class UIInput extends UIOutput im
         setLocalValueSet(false);
         setValid(true);
     }
+    
+    @Override
+    public ValueExpression getValueExpression(String name) 
+    {
+        // override this method to be able to install a _DebugValueExpressionWrapper
+        
+        ValueExpression valueExpression = super.getValueExpression(name);
+        if (FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development)
+                && "value".equals(name))
+        {
+            // use a _DebugValueExpressionWrapper for debugging of the value-ValueExpression
+            valueExpression = new _DebugValueExpressionWrapper(valueExpression);
+        }
+        return valueExpression;
+    }
 
     /**
      * A boolean value that identifies the phase during which action events should fire.
@@ -768,6 +802,12 @@ public class UIInput extends UIOutput im
 
     public void setSubmittedValue(Object submittedValue)
     {
+        if (FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development))
+        {
+            // extended debug-info when in Development mode
+            _createFieldDebugInfo(FacesContext.getCurrentInstance(), "submittedValue",
+                    getSubmittedValue(), submittedValue, 1);
+        }
         getStateHelper().put(PropertyKeys.submittedValue, submittedValue );
     }
 
@@ -903,6 +943,110 @@ public class UIInput extends UIOutput im
             return saveAttachedState(facesContext,_validatorList);
         }            
     }
+    
+    /**
+     * Returns the debug-info Map for this component.
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    private Map<String, List<String>> _getDebugInfoMap()
+    {
+        final Map<String, Object> requestMap = FacesContext.getCurrentInstance()
+                .getExternalContext().getRequestMap();
+        Map<String, List<String>> debugInfo = (Map<String, List<String>>) 
+                requestMap.get(DEBUG_INFO_KEY + getClientId());
+        if (debugInfo == null)
+        {
+            // no debug info available yet, create one and put it on the attributes map
+            debugInfo = new HashMap<String, List<String>>();
+            requestMap.put(DEBUG_INFO_KEY + getClientId(), debugInfo);
+        }
+        return debugInfo;
+    }
+    
+    /**
+     * Returns the field's debug-infos from the component's debug-info Map.
+     * @param field
+     * @return
+     */
+    private List<String> _getFieldDebugInfos(final String field)
+    {
+        Map<String, List<String>> debugInfo = _getDebugInfoMap();
+        List<String> fieldDebugInfo = debugInfo.get(field);
+        if (fieldDebugInfo == null)
+        {
+            // no field debug-infos yet, create them and store it in the Map
+            fieldDebugInfo = new ArrayList<String>();
+            debugInfo.put(field, fieldDebugInfo);
+        }
+        return fieldDebugInfo;
+    }
+    
+    /**
+     * Creates the field debug-info for the given field, which changed
+     * from oldValue to newValue.
+     * 
+     * @param facesContext
+     * @param field
+     * @param oldValue
+     * @param newValue
+     * @param skipStackTaceElements How many StackTraceElements should be skipped
+     *                              when the calling function will be determined.
+     */
+    private void _createFieldDebugInfo(FacesContext facesContext,
+            final String field, Object oldValue, 
+            Object newValue, final int skipStackTaceElements)
+    {
+        if (oldValue == null && newValue == null)
+        {
+            // both values are null, not interesting and can
+            // happen a lot in UIData with saving and restoring state
+            return;
+        }
+        
+        // convert Array values into a more readable format
+        if (oldValue != null && oldValue.getClass().isArray())
+        {
+            oldValue = Arrays.deepToString((Object[]) oldValue);
+        }
+        if (newValue != null && newValue.getClass().isArray())
+        {
+            newValue = Arrays.deepToString((Object[]) newValue);
+        }
+        
+        StringBuilder sb = new StringBuilder();
+        sb.append("<b>");
+        sb.append(field);
+        sb.append("</b> set from <b>");
+        sb.append(oldValue);
+        sb.append("</b> to <b>");
+        sb.append(newValue);
+        sb.append("</b> in Phase ");
+        sb.append(facesContext.getCurrentPhaseId());
+        sb.append(" with call from:<ul>");
+        
+        // use Throwable to get the current call stack
+        Throwable throwableHelper = new Throwable();
+        StackTraceElement[] stackTraceElements = throwableHelper.getStackTrace();
+        // + 1 because this method should also be skipped
+        for (int i = skipStackTaceElements + 1; i < stackTraceElements.length; i++)
+        {
+            sb.append("<li>");
+            sb.append(stackTraceElements[i].toString());
+            sb.append("</li>");
+            
+            if (FacesServlet.class.getCanonicalName()
+                    .equals(stackTraceElements[i].getClassName()))
+            {
+                // stop after the FacesServlet
+                break;
+            }
+        }
+        sb.append("</ul>");
+        
+        // add the debug info
+        _getFieldDebugInfos(field).add(sb.toString());
+    }
 
     /**
      * Check if a value is empty or not. Since we don't know the class of
@@ -948,4 +1092,83 @@ public class UIInput extends UIOutput im
         }
         return false;
     }
+    
+    /**
+     * This ValueExpression wrapper is installed for debugging reasons when
+     * in Development mode. Every call to setValue() will be forwarded
+     * to _createFieldDebugInfo() in order to create debugging infos.
+     *
+     * @author Jakob Korherr
+     */
+    private class _DebugValueExpressionWrapper extends ValueExpression
+    {
+        
+        private static final long serialVersionUID = -7114362926319263354L;
+        
+        private ValueExpression _wrapped;
+        
+        _DebugValueExpressionWrapper(ValueExpression wrapped)
+        {
+            _wrapped = wrapped;
+        }
+        
+        @Override
+        public Class<?> getExpectedType()
+        {
+            return _wrapped.getExpectedType();
+        }
+
+        @Override
+        public Class<?> getType(ELContext ctx)
+        {
+            return _wrapped.getType(ctx);
+        }
+
+        @Override
+        public Object getValue(ELContext ctx)
+        {
+            return _wrapped.getValue(ctx);
+        }
+
+        @Override
+        public boolean isReadOnly(ELContext ctx)
+        {
+            return _wrapped.isReadOnly(ctx);
+        }
+
+        @Override
+        public void setValue(ELContext ctx, Object value)
+        {
+            // create debug info for this call to setValue
+            _createFieldDebugInfo((FacesContext) ctx.getContext(FacesContext.class),
+                    "value", _wrapped.getValue(ctx), value, 2);
+            
+            _wrapped.setValue(ctx, value);
+        }
+
+        @Override
+        public boolean equals(Object o)
+        {
+            return _wrapped.equals(o);
+        }
+
+        @Override
+        public String getExpressionString()
+        {
+            return _wrapped.getExpressionString();
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return _wrapped.hashCode();
+        }
+
+        @Override
+        public boolean isLiteralText()
+        {
+            return _wrapped.isLiteralText();
+        }
+
+    }
 }

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/FacesConfigurator.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/FacesConfigurator.java?rev=941020&r1=941019&r2=941020&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/FacesConfigurator.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/FacesConfigurator.java Tue May  4 20:06:31 2010
@@ -55,6 +55,7 @@ import javax.faces.application.Applicati
 import javax.faces.application.ApplicationFactory;
 import javax.faces.application.ConfigurableNavigationHandler;
 import javax.faces.application.NavigationHandler;
+import javax.faces.application.ProjectStage;
 import javax.faces.application.ResourceHandler;
 import javax.faces.application.StateManager;
 import javax.faces.application.ViewHandler;
@@ -116,6 +117,7 @@ import org.apache.myfaces.util.Container
 import org.apache.myfaces.util.ExternalSpecifications;
 import org.apache.myfaces.view.ViewDeclarationLanguageFactoryImpl;
 import org.apache.myfaces.view.facelets.tag.jsf.TagHandlerDelegateFactoryImpl;
+import org.apache.myfaces.view.facelets.tag.ui.DebugPhaseListener;
 import org.apache.myfaces.view.facelets.util.Classpath;
 import org.xml.sax.SAXException;
 
@@ -2374,6 +2376,15 @@ public class FacesConfigurator
                 log.severe("Class " + listenerClassName + " does not implement PhaseListener");
             }
         }
+        
+        // if ProjectStage is Development, install the DebugPhaseListener
+        // Note that FacesContext.getCurrentInstance() will be null for the very first
+        // initialisation here. In that case the PhaseListener is installed in AbstractFacesInitializer.
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        if (facesContext != null && facesContext.isProjectStage(ProjectStage.Development))
+        {
+            lifecycle.addPhaseListener(new DebugPhaseListener());
+        }
     }
 
     private String getLifecycleId()

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/renderkit/ErrorPageWriter.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/renderkit/ErrorPageWriter.java?rev=941020&r1=941019&r2=941020&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/renderkit/ErrorPageWriter.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/renderkit/ErrorPageWriter.java Tue May  4 20:06:31 2010
@@ -34,6 +34,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
+import java.util.EnumSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -47,20 +48,29 @@ import java.util.regex.Pattern;
 import javax.el.Expression;
 import javax.el.ValueExpression;
 import javax.faces.FacesException;
+import javax.faces.component.UIColumn;
 import javax.faces.component.UIComponent;
+import javax.faces.component.UIData;
+import javax.faces.component.UIInput;
 import javax.faces.component.UIViewRoot;
+import javax.faces.component.visit.VisitCallback;
+import javax.faces.component.visit.VisitContext;
+import javax.faces.component.visit.VisitHint;
+import javax.faces.component.visit.VisitResult;
 import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
 import javax.faces.context.PartialResponseWriter;
 import javax.faces.context.ResponseWriter;
 import javax.faces.el.MethodBinding;
 import javax.faces.el.ValueBinding;
+import javax.faces.render.Renderer;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
 import org.apache.myfaces.shared_impl.renderkit.html.HtmlResponseWriterImpl;
 import org.apache.myfaces.shared_impl.util.ClassUtils;
 import org.apache.myfaces.shared_impl.webapp.webxml.WebXml;
+import org.apache.myfaces.view.facelets.component.UIRepeat;
 
 /**
  * This class provides utility methods to generate the
@@ -197,6 +207,20 @@ public final class ErrorPageWriter
     private final static String[] IGNORE = new String[] { "parent", "rendererType" };
     
     /**
+     * Extended debug info is stored under this key in the request
+     * map for every UIInput component when in Development mode.
+     * ATTENTION: this constant is duplicate in javax.faces.component.UIInput
+     */
+    public static final String DEBUG_INFO_KEY = "org.apache.myfaces.debug.DEBUG_INFO";
+    
+    /**
+     * The number of facets of this component which have already been visited while
+     * creating the extended component tree is saved under this key in the component's
+     * attribute map.
+     */
+    private static final String VISITED_FACET_COUNT_KEY = "org.apache.myfaces.debug.VISITED_FACET_COUNT";
+    
+    /**
      * Indicate if myfaces is responsible to handle errors. 
      * See http://wiki.apache.org/myfaces/Handling_Server_Errors for details. 
      */
@@ -294,6 +318,10 @@ public final class ErrorPageWriter
             {
                 _writeComponent(writer, faces.getViewRoot(), null);
             }
+            else if ("extendedtree".equals(DEBUG_PARTS[i]))
+            {
+                _writeExtendedComponentTree(writer, faces);
+            }
             else if ("vars".equals(DEBUG_PARTS[i]))
             {
                 _writeVariables(writer, faces, faces.getViewRoot());
@@ -601,7 +629,7 @@ public final class ErrorPageWriter
 
         boolean hasChildren = c.getChildCount() > 0 || c.getFacets().size() > 0;
 
-        _writeStart(writer, c, hasChildren);
+        _writeStart(writer, c, hasChildren, true);
         writer.write("</dt>");
         if (hasChildren)
         {
@@ -632,6 +660,289 @@ public final class ErrorPageWriter
         }
         writer.write("</dl>");
     }
+    
+    /**
+     * Creates the Extended Component Tree via UIViewRoot.visitTree()
+     * and ExtendedComponentTreeVisitCallback as VisitCallback.
+     * 
+     * @param writer
+     * @param facesContext
+     * @throws IOException
+     */
+    private static void _writeExtendedComponentTree(Writer writer, 
+            FacesContext facesContext) throws IOException
+    {
+        VisitContext visitContext = VisitContext.createVisitContext(
+                facesContext, null, EnumSet.of(VisitHint.SKIP_UNRENDERED));
+        facesContext.getViewRoot().visitTree(visitContext, new ExtendedComponentTreeVisitCallback(writer));
+    }
+    
+    /**
+     * The VisitCallback that is used to create the Extended Component Tree.
+     * 
+     * @author Jakob Korherr
+     */
+    private static class ExtendedComponentTreeVisitCallback implements VisitCallback
+    {
+
+        private Writer _writer;
+        
+        public ExtendedComponentTreeVisitCallback(Writer writer)
+        {
+            _writer = writer;
+        }
+        
+        @SuppressWarnings("unchecked")
+        public VisitResult visit(VisitContext context, UIComponent target)
+        {
+            final Map<String, Object> requestMap = context.getFacesContext()
+                    .getExternalContext().getRequestMap();
+            
+            try
+            {
+                if (!(target instanceof UIViewRoot))
+                {
+                    _writer.write("<dd>");
+                }
+                
+                UIComponent parent = target.getParent();
+                boolean hasChildren = (target.getChildCount() > 0 || target.getFacets().size() > 0);
+                String facetName = _getFacetName(target);
+                
+                if (!(target instanceof UIColumn))
+                {
+                    if (parent instanceof UIColumn
+                            && (parent.getChildren().get(0) == target
+                                    ||  (facetName != null &&_getVisitedFacetCount(parent) == 0)))
+                    {
+                        if (parent.getParent() instanceof UIData
+                                && _isFirstUIColumn(parent.getParent(), (UIColumn) parent))
+                        {
+                            _writer.write("<span>Row: ");
+                            int rowIndex = ((UIData) parent.getParent()).getRowIndex();
+                            _writer.write("" + rowIndex);
+                            if (rowIndex == -1)
+                            {
+                                // tell the user that rowIndex == -1 stands for visiting column-facets
+                                _writer.write(" (all column facets)");
+                            }
+                            _writer.write("</span>");
+                        }
+                        _writer.write("<dl><dt>");
+                        _writeStart(_writer, parent, true, false);
+                        _writer.write("</dt><dd>");
+                    }
+                    
+                    if (facetName != null)
+                    {
+                        _writer.write("<span>" + facetName + "</span>"); 
+                        _incrementVisitedFacetCount(parent);
+                    }
+                    _writer.write("<dl><dt");
+                    if (_isText(target))
+                    {
+                        _writer.write(" class=\"uicText\"");
+                    }
+                    _writer.write(">");
+                    
+                    Map<String, List<String>> debugInfo = null;
+                    // is the target a UIInput component?
+                    if (target instanceof UIInput)
+                    {
+                        // get the debug info
+                        debugInfo = (Map<String, List<String>>) requestMap
+                                .get(DEBUG_INFO_KEY + target.getClientId());
+                    }
+                    
+                    // Get the component's renderer.
+                    // Note that getRenderer(FacesContext context) is definded in UIComponent,
+                    // but it is protected, so we have to use reflection!
+                    Renderer renderer = null;
+                    try
+                    {
+                        Method getRenderer = UIComponent.class.getDeclaredMethod(
+                                "getRenderer", FacesContext.class);
+                        // make it accessible for us!
+                        getRenderer.setAccessible(true);
+                        renderer = (Renderer) getRenderer.invoke(target, context.getFacesContext());
+                    }
+                    catch (Exception e)
+                    {
+                        // nothing - do not output renderer information
+                    }
+                    
+                    // write the component start
+                    _writeStart(_writer, target, (hasChildren || debugInfo != null || renderer != null), false);
+                    _writer.write("</dt>");
+                    
+                    if (renderer != null)
+                    {
+                        // write renderer info
+                        _writer.write("<div class=\"renderer\">Rendered by ");
+                        _writer.write(renderer.getClass().getCanonicalName());
+                        _writer.write("</div>");
+                        
+                        if (!hasChildren && debugInfo == null)
+                        {
+                            // close the component
+                            _writer.write("<dt>");
+                            _writeEnd(_writer, target);
+                            _writer.write("</dt>");
+                        }
+                    }
+                        
+                    if (debugInfo != null)
+                    {
+                        final String fieldid = target.getClientId() + "_lifecycle";
+                        _writer.write("<div class=\"lifecycle_values_wrapper\">");
+                        _writer.write("<a href=\"#\" onclick=\"toggle('");
+                        _writer.write(fieldid);
+                        _writer.write("'); return false;\"><span id=\"");
+                        _writer.write(fieldid);
+                        _writer.write("Off\">+</span><span id=\"");
+                        _writer.write(fieldid);
+                        _writer.write("On\" style=\"display: none;\">-</span> Value Lifecycle</a>");
+                        _writer.write("<div id=\"");
+                        _writer.write(fieldid);
+                        _writer.write("\" class=\"lifecycle_values\">");
+                        
+                        // process any available debug info
+                        for (Map.Entry<String, List<String>> entry : debugInfo.entrySet())
+                        {
+                            _writer.write("<span>");
+                            _writer.write(entry.getKey());
+                            _writer.write("</span><ol>");
+                            for (String change : entry.getValue())
+                            {
+                                _writer.write("<li>");
+                                _writer.write(change);
+                                _writer.write("</li>");
+                            }
+                            _writer.write("</ol>");
+                        }
+                        
+                        _writer.write("</div></div>");
+                        
+                        // now remove the debug info from the request map, 
+                        // so that it does not appear in the scope values of the debug page 
+                        requestMap.remove(DEBUG_INFO_KEY + target.getClientId());
+                        
+                        if (!hasChildren)
+                        {
+                            // close the component
+                            _writer.write("<dt>");
+                            _writeEnd(_writer, target);
+                            _writer.write("</dt>");
+                        }
+                    }
+                }
+                
+                if (!hasChildren)
+                {
+                    _writer.write("</dl>");
+                    
+                    while (parent != null 
+                            && (parent.getChildren().get(parent.getChildCount() - 1) == target
+                                    || (parent.getFacetCount() != 0 
+                                            && _getVisitedFacetCount(parent) == parent.getFacetCount())))
+                    {
+                        // target is last child of parent or the "last" facet
+                        
+                        // remove the visited facet count from the attribute map
+                        _removeVisitedFacetCount(parent);
+                        
+                        // check for componentes that visit their children multiple times
+                        if (parent instanceof UIData)
+                        {
+                            UIData uidata = (UIData) parent;
+                            if (uidata.getRowIndex() != uidata.getRowCount() - 1)
+                            {
+                                // only continue if we're in the last row
+                                break;
+                            }
+                        }
+                        else if (parent instanceof UIRepeat)
+                        {
+                            UIRepeat uirepeat = (UIRepeat) parent;
+                            if (uirepeat.getIndex() + uirepeat.getStep() < uirepeat.getRowCount())
+                            {
+                                // only continue if we're in the last row
+                                break;
+                            }
+                        }
+                        
+                        _writer.write("</dd><dt>");
+                        _writeEnd(_writer, parent);
+                        _writer.write("</dt></dl>");
+                        
+                        if (!(parent instanceof UIViewRoot))
+                        {
+                            _writer.write("</dd>");
+                        }
+                        
+                        target = parent;
+                        parent = target.getParent(); 
+                    }
+                }
+            }
+            catch (IOException ioe)
+            {
+                throw new FacesException(ioe);
+            }
+            
+            return VisitResult.ACCEPT;
+        }
+        
+    }
+    
+    private static boolean _isFirstUIColumn(UIComponent uidata, UIColumn uicolumn)
+    {
+        for (UIComponent child : uidata.getChildren())
+        {
+            if (child instanceof UIColumn)
+            {
+                return (child == uicolumn);
+            }
+        }
+        return false;
+    }
+    
+    private static String _getFacetName(UIComponent component)
+    {
+        UIComponent parent = component.getParent();
+        if (parent != null)
+        {
+            for (Map.Entry<String, UIComponent> entry : parent.getFacets().entrySet())
+            {
+                if (entry.getValue() == component)
+                {
+                    return entry.getKey();
+                }
+            }
+        }
+        return null;
+    }
+    
+    private static int _getVisitedFacetCount(UIComponent component)
+    {
+        Integer count = (Integer) component.getAttributes().get(VISITED_FACET_COUNT_KEY);
+        if (count != null)
+        {
+            return count;
+        }
+        return 0;
+    }
+    
+    private static void _incrementVisitedFacetCount(UIComponent component)
+    {
+        component.getAttributes().put(VISITED_FACET_COUNT_KEY, 
+                _getVisitedFacetCount(component) + 1);
+    }
+    
+    private static void _removeVisitedFacetCount(UIComponent component)
+    {
+        component.getAttributes().remove(VISITED_FACET_COUNT_KEY);
+    }
 
     private static void _writeEnd(Writer writer, UIComponent c) throws IOException
     {
@@ -644,7 +955,7 @@ public final class ErrorPageWriter
         }
     }
 
-    private static void _writeAttributes(Writer writer, UIComponent c)
+    private static void _writeAttributes(Writer writer, UIComponent c, boolean valueExpressionValues)
     {
         try
         {
@@ -663,7 +974,7 @@ public final class ErrorPageWriter
                     {
                         // first check if the property is a ValueExpression
                         valueExpression = c.getValueExpression(pd[i].getName());
-                        if (valueExpression != null)
+                        if (valueExpressionValues && valueExpression != null)
                         {
                             _writeAttribute(writer, pd[i].getName(), valueExpression.getExpressionString());
                         }
@@ -692,6 +1003,7 @@ public final class ErrorPageWriter
                                 {
                                     str = v.toString();
                                 }
+                                
                                 _writeAttribute(writer, pd[i].getName(), str);
                             }
                         }
@@ -724,7 +1036,8 @@ public final class ErrorPageWriter
         writer.write("\"");
     }
 
-    private static void _writeStart(Writer writer, UIComponent c, boolean children) throws IOException
+    private static void _writeStart(Writer writer, UIComponent c, 
+            boolean children, boolean valueExpressionValues) throws IOException
     {
         if (_isText(c))
         {
@@ -735,7 +1048,7 @@ public final class ErrorPageWriter
         {
             writer.write(TS);
             writer.write(_getName(c));
-            _writeAttributes(writer, c);
+            _writeAttributes(writer, c, valueExpressionValues);
             if (children)
             {
                 writer.write('>');

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/component/UIRepeat.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/component/UIRepeat.java?rev=941020&r1=941019&r2=941020&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/component/UIRepeat.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/component/UIRepeat.java Tue May  4 20:06:31 2010
@@ -456,6 +456,23 @@ public class UIRepeat extends UIComponen
         }
     }
     
+    /**
+     * Returns the rowCount of the underlying DataModel.
+     * @return
+     */
+    public int getRowCount()
+    {
+        return getDataModel().getRowCount();
+    }
+    
+    /**
+     * Returns the current index.
+     */
+    public int getIndex()
+    {
+        return _index;
+    }
+    
     private void _setIndex(int index)
     {
         // save child state

Added: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java?rev=941020&view=auto
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java (added)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java Tue May  4 20:06:31 2010
@@ -0,0 +1,92 @@
+/*
+ * 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.myfaces.view.facelets.tag.ui;
+
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIViewRoot;
+import javax.faces.component.visit.VisitCallback;
+import javax.faces.component.visit.VisitContext;
+import javax.faces.component.visit.VisitResult;
+import javax.faces.event.PhaseEvent;
+import javax.faces.event.PhaseId;
+import javax.faces.event.PhaseListener;
+
+/**
+ * PhaseListener to create extended debug information.
+ * Installed in FacesConfigurator.configureLifecycle() if ProjectStage is Development.
+ * 
+ * @author Jakob Korherr (latest modification by $Author$)
+ * @version $Revision$ $Date$
+ */
+public class DebugPhaseListener implements PhaseListener
+{
+    
+    private static final long serialVersionUID = -1517198431551012882L;
+
+    /**
+     * VisitCallback used for visitTree()  
+     *  
+     * @author Jakob Korherr
+     */
+    private class DebugVisitCallback implements VisitCallback
+    {
+
+        public VisitResult visit(VisitContext context, UIComponent target)
+        {
+            // TODO implement for components that do not extend UIInput
+            
+            return VisitResult.ACCEPT;
+        }
+        
+    }
+    
+    private boolean _afterPhase = false;
+    private PhaseId _currentPhase;
+    private DebugVisitCallback _visitCallback = new DebugVisitCallback();
+
+    public void afterPhase(PhaseEvent event)
+    {
+        _doTreeVisit(event, true);
+    }
+
+    public void beforePhase(PhaseEvent event)
+    {
+        _doTreeVisit(event, false);
+    }
+
+    public PhaseId getPhaseId()
+    {
+        return PhaseId.ANY_PHASE;
+    }
+    
+    private void _doTreeVisit(PhaseEvent event, boolean afterPhase)
+    {
+        _afterPhase = afterPhase;
+        _currentPhase = event.getPhaseId();
+        
+        // visitTree() on the UIViewRoot
+        UIViewRoot viewroot = event.getFacesContext().getViewRoot();
+        if (viewroot != null)
+        {
+            viewroot.visitTree(VisitContext.createVisitContext(
+                    event.getFacesContext()), _visitCallback);
+        }
+    }
+
+}

Propchange: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/webapp/AbstractFacesInitializer.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/webapp/AbstractFacesInitializer.java?rev=941020&r1=941019&r2=941020&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/webapp/AbstractFacesInitializer.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/webapp/AbstractFacesInitializer.java Tue May  4 20:06:31 2010
@@ -34,7 +34,9 @@ import javax.faces.context.FacesContext;
 import javax.faces.context.FacesContextFactory;
 import javax.faces.event.PostConstructApplicationEvent;
 import javax.faces.event.PreDestroyApplicationEvent;
+import javax.faces.lifecycle.Lifecycle;
 import javax.faces.lifecycle.LifecycleFactory;
+import javax.faces.webapp.FacesServlet;
 import javax.servlet.ServletContext;
 
 import org.apache.myfaces.application.ApplicationImpl;
@@ -48,6 +50,7 @@ import org.apache.myfaces.config.annotat
 import org.apache.myfaces.context.servlet.ServletExternalContextImpl;
 import org.apache.myfaces.shared_impl.util.StateUtils;
 import org.apache.myfaces.shared_impl.webapp.webxml.WebXml;
+import org.apache.myfaces.view.facelets.tag.ui.DebugPhaseListener;
 
 /**
  * Performs common initialization tasks.
@@ -160,6 +163,15 @@ public abstract class AbstractFacesIniti
                 message.append("*** See Application#getProjectStage() for more information.     ***\n");
                 message.append("*******************************************************************\n");
                 log.log(Level.WARNING, message.toString());
+                
+                // if ProjectStage is Development, install the DebugPhaseListener
+                if (FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development))
+                {
+                    LifecycleFactory lifeFac = (LifecycleFactory) FactoryFinder
+                            .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
+                    Lifecycle lifecycle = lifeFac.getLifecycle(getLifecycleId(servletContext));
+                    lifecycle.addPhaseListener(new DebugPhaseListener());
+                }
             }
             
             releaseFacesContext();
@@ -186,7 +198,7 @@ public abstract class AbstractFacesIniti
      * @param eventClass     the class to be passed down into the dispatching
      *                       code
      */
-    private void dispatchInitDestroyEvent(Object servletContext, Class eventClass) {
+    private void dispatchInitDestroyEvent(ServletContext servletContext, Class eventClass) {
         ApplicationFactory appFac = (ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
         FacesContext fc = null;
 
@@ -194,7 +206,10 @@ public abstract class AbstractFacesIniti
         if (fc == null) {
             LifecycleFactory lifeFac = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
             FacesContextFactory facFac = (FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
-            fc = facFac.getFacesContext(servletContext, new _SystemEventServletRequest(), new _SystemEventServletResponse(), lifeFac.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE));
+            fc = facFac.getFacesContext(servletContext, 
+                    new _SystemEventServletRequest(), 
+                    new _SystemEventServletResponse(), 
+                    lifeFac.getLifecycle(getLifecycleId(servletContext)));
         }
         
         // in order to allow FacesContext.getViewRoot calls during startup/shutdown listeners, 
@@ -205,6 +220,23 @@ public abstract class AbstractFacesIniti
         
         appFac.getApplication().publishEvent(fc, eventClass, Application.class, appFac.getApplication());
     }
+    
+    /**
+     * Gets the LifecycleId from the ServletContext init param.
+     * If this is null, it returns LifecycleFactory.DEFAULT_LIFECYCLE.
+     * @param servletContext
+     * @return
+     */
+    private String getLifecycleId(ServletContext servletContext)
+    {
+        String id = servletContext.getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
+
+        if (id != null)
+        {
+            return id;
+        }
+        return LifecycleFactory.DEFAULT_LIFECYCLE;
+    }
 
     /**
      * Cleans up all remaining resources (well, theoretically).

Modified: myfaces/core/trunk/impl/src/main/resources/META-INF/rsc/myfaces-dev-debug.xml
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/resources/META-INF/rsc/myfaces-dev-debug.xml?rev=941020&r1=941019&r2=941020&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/resources/META-INF/rsc/myfaces-dev-debug.xml (original)
+++ myfaces/core/trunk/impl/src/main/resources/META-INF/rsc/myfaces-dev-debug.xml Tue May  4 20:06:31 2010
@@ -5,17 +5,16 @@
 <title>Debug - @@message@@</title>
 <style type="text/css">
 body, div, span, td, th, caption { font-family: 'Trebuchet MS', Verdana, Arial, Sans-Serif; font-size: small; }
-ul, li, pre { padding: 0; margin: 0; }
 h1 { color: #090; }
 h2, h2 span { font-size: large; color: #339; }
-h2 a { text-decoration: none; color: #339; }
+h2 a, .grayBox a { text-decoration: none; color: #339; }
 .grayBox { padding: 8px; margin: 10px 0; border: 1px solid #CCC; background-color: #f9f9f9;  }
 #view { color: #090; font-weight: bold; font-size: medium; }
-#tree, #vars { display: none; }
+#tree, #extendedtree, #vars { display: none; }
 code { font-size: medium; }
-#tree dl { color: #006; }
-#tree dd { margin-top: 2px; margin-bottom: 2px; }
-#tree dt { border: 1px solid #DDD; padding: 4px; border-left: 2px solid #666; font-family: "Courier New", Courier, mono; font-size: small; }
+#tree dl, #extendedtree dl { color: #006; }
+#tree dd, #extendedtree dd { margin-top: 2px; margin-bottom: 2px; }
+#tree dt, #extendedtree dt { border: 1px solid #DDD; padding: 4px; border-left: 2px solid #666; font-family: "Courier New", Courier, mono; font-size: small; }
 .uicText { color: #999;  }
 .highlightComponent { color: #FFFFFF; background-color: #FF0000;  }
 table { border: 1px solid #CCC; border-collapse: collapse; border-spacing: 0px; width: 100%; text-align: left; }
@@ -23,6 +22,8 @@ td { border: 1px solid #CCC; }
 thead tr th { padding: 2px; color: #030; background-color: #F9F9F9; }
 tbody tr td { padding: 10px 6px; }
 table caption { text-align: left; padding: 10px 0; font-size: large; }
+.lifecycle_values { display:none; margin: 5px 5px 5px 20px; }
+.lifecycle_values_wrapper, .renderer { margin: 5px 0px 5px 40px; }
 </style>
 <style type="text/css" media="print">
 #trace, #tree, #vars { display: block; }
@@ -47,6 +48,8 @@ function toggle(id) {
 <div id="error" class="grayBox" style="border: 1px solid #090;">@@message@@</div>
 <h2><a href="#" onclick="toggle('tree'); return false;"><span id="treeOff">+</span><span id="treeOn" style="display: none;">-</span> Component Tree</a></h2>
 <div id="tree" class="grayBox"><code>@@tree@@</code></div>
+<h2><a href="#" onclick="toggle('extendedtree'); return false;"><span id="extendedtreeOff">+</span><span id="extendedtreeOn" style="display: none;">-</span> Extended Component Tree</a></h2>
+<div id="extendedtree" class="grayBox"><code>@@extendedtree@@</code></div>
 <h2><a href="#" onclick="toggle('vars'); return false;"><span id="varsOff">+</span><span id="varsOn" style="display: none;">-</span> Scoped Variables</a></h2>
 <div id="vars">@@vars@@</div>
 <div class="grayBox" style="text-align: right; color: #666;">@@now@@ - Generated by MyFaces - for information on disabling or modifying this error-page, see <a