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]; + } }