Author: jdonnerstag
Date: Sat Jun  4 19:54:56 2011
New Revision: 1131483

URL: http://svn.apache.org/viewvc?rev=1131483&view=rev
Log:
WICKET-3674
Improved error message: A little heuristic for "Unable to find component" 
exception

Modified:
    
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java
    
wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/string/Strings.java

Modified: 
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java
URL: 
http://svn.apache.org/viewvc/wicket/trunk/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java?rev=1131483&r1=1131482&r2=1131483&view=diff
==============================================================================
--- 
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java 
(original)
+++ 
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java 
Sat Jun  4 19:54:56 2011
@@ -40,6 +40,7 @@ import org.apache.wicket.model.IComponen
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.IWrapModel;
 import org.apache.wicket.settings.IDebugSettings;
+import org.apache.wicket.util.lang.Generics;
 import org.apache.wicket.util.string.ComponentStrings;
 import org.apache.wicket.util.string.Strings;
 import org.apache.wicket.util.visit.ClassVisitFilter;
@@ -1450,11 +1451,24 @@ public abstract class MarkupContainer ex
                                        }
                                }
 
+                               List<String> names = findSimilarComponents(id);
+
                                // No one was able to handle the component id
-                               markupStream.throwMarkupException("Unable to 
find component with id '" + id +
-                                       "' in " + this + ". This means that you 
declared wicket:id=" + id +
-                                       " in your markup, but that you either 
did not add the " +
-                                       "component to your page at all, or that 
the hierarchy does not match.");
+                               StringBuffer msg = new StringBuffer(500);
+                               msg.append("Unable to find component with id 
'");
+                               msg.append(id);
+                               msg.append("' in ");
+                               msg.append(this.toString());
+                               msg.append("\n\tExpected: '");
+                               msg.append(getPageRelativePath());
+                               msg.append(".");
+                               msg.append(id);
+                               msg.append("'.\n\tFound with similar names: '");
+                               msg.append(Strings.join("', ", names));
+                               msg.append("'");
+
+                               log.error(msg.toString());
+                               
markupStream.throwMarkupException(msg.toString());
                        }
                }
                else
@@ -1467,6 +1481,29 @@ public abstract class MarkupContainer ex
                return false;
        }
 
+       private List<String> findSimilarComponents(final String id)
+       {
+               final List<String> names = Generics.newArrayList();
+
+               Page page = findPage();
+               if (page != null)
+               {
+                       page.visitChildren(new IVisitor<Component, Void>()
+                       {
+                               public void component(Component component, 
IVisit<Void> visit)
+                               {
+                                       if 
(Strings.getLevenshteinDistance(id.toLowerCase(), component.getId()
+                                               .toLowerCase()) < 3)
+                                       {
+                                               
names.add(component.getPageRelativePath());
+                                       }
+                               }
+                       });
+               }
+
+               return names;
+       }
+
        /**
         * Handle the container's body. If your override of this method does 
not advance the markup
         * stream to the close tag for the openTag, a runtime exception will be 
thrown by the framework.

Modified: 
wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/string/Strings.java
URL: 
http://svn.apache.org/viewvc/wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/string/Strings.java?rev=1131483&r1=1131482&r2=1131483&view=diff
==============================================================================
--- 
wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/string/Strings.java
 (original)
+++ 
wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/string/Strings.java
 Sat Jun  4 19:54:56 2011
@@ -1361,4 +1361,135 @@ public final class Strings
 
                return -1;
        }
+
+       /**
+        * <p>
+        * Find the Levenshtein distance between two Strings.
+        * </p>
+        * 
+        * <p>
+        * This is the number of changes needed to change one String into 
another, where each change is
+        * a single character modification (deletion, insertion or 
substitution).
+        * </p>
+        * 
+        * <p>
+        * The previous implementation of the Levenshtein distance algorithm 
was from <a
+        * 
href="http://www.merriampark.com/ld.htm";>http://www.merriampark.com/ld.htm</a>
+        * </p>
+        * 
+        * <p>
+        * Chas Emerick has written an implementation in Java, which avoids an 
OutOfMemoryError which
+        * can occur when my Java implementation is used with very large 
strings.<br>
+        * This implementation of the Levenshtein distance algorithm is from <a
+        * 
href="http://www.merriampark.com/ldjava.htm";>http://www.merriampark.com/ldjava.htm</a>
+        * </p>
+        * 
+        * <pre>
+        * StringUtils.getLevenshteinDistance(null, *)             = 
IllegalArgumentException
+        * StringUtils.getLevenshteinDistance(*, null)             = 
IllegalArgumentException
+        * StringUtils.getLevenshteinDistance("","")               = 0
+        * StringUtils.getLevenshteinDistance("","a")              = 1
+        * StringUtils.getLevenshteinDistance("aaapppp", "")       = 7
+        * StringUtils.getLevenshteinDistance("frog", "fog")       = 1
+        * StringUtils.getLevenshteinDistance("fly", "ant")        = 3
+        * StringUtils.getLevenshteinDistance("elephant", "hippo") = 7
+        * StringUtils.getLevenshteinDistance("hippo", "elephant") = 7
+        * StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
+        * StringUtils.getLevenshteinDistance("hello", "hallo")    = 1
+        * </pre>
+        * 
+        * Copied from Apache commons-lang StringUtils 3.0
+        * 
+        * @param s
+        *            the first String, must not be null
+        * @param t
+        *            the second String, must not be null
+        * @return result distance
+        * @throws IllegalArgumentException
+        *             if either String input {@code null}
+        */
+       public static int getLevenshteinDistance(CharSequence s, CharSequence t)
+       {
+               if (s == null || t == null)
+               {
+                       throw new IllegalArgumentException("Strings must not be 
null");
+               }
+
+               /*
+                * The difference between this impl. and the previous is that, 
rather than creating and
+                * retaining a matrix of size s.length()+1 by t.length()+1, we 
maintain two
+                * single-dimensional arrays of length s.length()+1. The first, 
d, is the 'current working'
+                * distance array that maintains the newest distance cost 
counts as we iterate through the
+                * characters of String s. Each time we increment the index of 
String t we are comparing, d
+                * is copied to p, the second int[]. Doing so allows us to 
retain the previous cost counts
+                * as required by the algorithm (taking the minimum of the cost 
count to the left, up one,
+                * and diagonally up and to the left of the current cost count 
being calculated). (Note that
+                * the arrays aren't really copied anymore, just 
switched...this is clearly much better than
+                * cloning an array or doing a System.arraycopy() each time 
through the outer loop.)
+                * 
+                * Effectively, the difference between the two implementations 
is this one does not cause an
+                * out of memory condition when calculating the LD over two 
very large strings.
+                */
+
+               int n = s.length(); // length of s
+               int m = t.length(); // length of t
+
+               if (n == 0)
+               {
+                       return m;
+               }
+               else if (m == 0)
+               {
+                       return n;
+               }
+
+               if (n > m)
+               {
+                       // swap the input strings to consume less memory
+                       CharSequence tmp = s;
+                       s = t;
+                       t = tmp;
+                       n = m;
+                       m = t.length();
+               }
+
+               int p[] = new int[n + 1]; // 'previous' cost array, horizontally
+               int d[] = new int[n + 1]; // cost array, horizontally
+               int _d[]; // placeholder to assist in swapping p and d
+
+               // indexes into strings s and t
+               int i; // iterates through s
+               int j; // iterates through t
+
+               char t_j; // jth character of t
+
+               int cost; // cost
+
+               for (i = 0; i <= n; i++)
+               {
+                       p[i] = i;
+               }
+
+               for (j = 1; j <= m; j++)
+               {
+                       t_j = t.charAt(j - 1);
+                       d[0] = j;
+
+                       for (i = 1; i <= n; i++)
+                       {
+                               cost = s.charAt(i - 1) == t_j ? 0 : 1;
+                               // minimum of cell to the left+1, to the top+1, 
diagonally left and up +cost
+                               d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 
1), p[i - 1] + cost);
+                       }
+
+                       // copy current distance counts to 'previous row' 
distance counts
+                       _d = p;
+                       p = d;
+                       d = _d;
+               }
+
+               // our last action in the above loop was to switch d and p, so 
p now
+               // actually has the most recent cost counts
+               return p[n];
+       }
 }


Reply via email to