TAP5-1978: Make Errors component support displaying "global" errors only
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTracker.java b/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTracker.java
index 6cdf0da..2230d13 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTracker.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTracker.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008 The Apache Software Foundation
+// Copyright 2006, 2008, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -39,8 +39,10 @@
      * Called by a field to record the exact input from the user, prior to any validation. If the form is redisplayed
      * (to present errors), the input value will be sent back to the user for correction.
      *
-     * @param field the field recording the input
-     * @param input the value obtained from the forms submission
+     * @param field
+     *         the field recording the input
+     * @param input
+     *         the value obtained from the forms submission
      */
     void recordInput(Field field, String input);
 
@@ -92,6 +94,13 @@
     List<String> getErrors();
 
     /**
+     * Returns just the errors that are not associated with any fields.
+     *
+     * @since 5.4
+     */
+    List<String> getUnassociatedErrors();
+
+    /**
      * Clears all information stored by the tracker.
      */
     void clear();
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java
index 0f29acc..33666a4 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008, 2010, 2011 The Apache Software Foundation
+// Copyright 2006-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 
 import java.io.Serializable;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -135,6 +136,16 @@
         return result;
     }
 
+    public List<String> getUnassociatedErrors()
+    {
+        if (extraErrors == null)
+        {
+            return Collections.emptyList();
+        }
+
+        return Collections.unmodifiableList(extraErrors);
+    }
+
     public boolean getHasErrors()
     {
         return !getErrors().isEmpty();
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerWrapper.java b/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerWrapper.java
index e00b8fa..5d19cb0 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerWrapper.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerWrapper.java
@@ -1,4 +1,4 @@
-// Copyright 2010 The Apache Software Foundation
+// Copyright 2010, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -46,6 +46,11 @@
         return delegate.getErrors();
     }
 
+    public List<String> getUnassociatedErrors()
+    {
+        return delegate.getUnassociatedErrors();
+    }
+
     public boolean getHasErrors()
     {
         return delegate.getHasErrors();
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Errors.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Errors.java
index b061112..d2abbf5 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Errors.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Errors.java
@@ -47,6 +47,16 @@
     private String banner;
 
     /**
+     * If true, then only errors global to the form (unassociated with any specific field) are
+     * presented. By default all errors (associated with fields, or not) are presented; with unassoicated
+     * errors presented first.
+     *
+     * @since 5.4
+     */
+    @Parameter
+    private boolean globalOnly;
+
+    /**
      * The CSS class for the div element rendered by the component.
      */
     @Parameter(name = "class", defaultPrefix = BindingConstants.LITERAL, value = "alert alert-danger")
@@ -66,6 +76,13 @@
             return false;
         }
 
+        List<String> errors =
+                globalOnly ? tracker.getUnassociatedErrors() : tracker.getErrors();
+
+        if (errors.isEmpty())
+        {
+            return false;
+        }
 
         Set<String> previousErrors = CollectionFactory.newSet();
 
@@ -81,8 +98,6 @@
         writer.writeRaw(banner);
         writer.end();
 
-        List<String> errors = tracker.getErrors();
-
         writer.element("ul");
 
         for (String message : errors)
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/root/ValidationTrackerImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/root/ValidationTrackerImplTest.java
index b4b0185..e9b33f6 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/root/ValidationTrackerImplTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/root/ValidationTrackerImplTest.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -109,6 +109,26 @@
     }
 
     @Test
+    public void unsassoicated_errors_listed_first() {
+
+        ValidationTracker tracker = new ValidationTrackerImpl();
+
+        Field field = newFieldWithControlName("field");
+
+        replay();
+
+        tracker.recordError(field, "one");
+
+        tracker.recordError("two");
+
+        assertEquals(tracker.getErrors(), Arrays.asList("two", "one"));
+
+        assertEquals(tracker.getUnassociatedErrors(), Arrays.asList("two"));
+
+        verify();
+    }
+
+    @Test
     public void record_error_for_form()
     {
         ValidationTracker tracker = new ValidationTrackerImpl();