Cherry pick r9209 into releases/2.1, uibinder fix for fix negative values in LayoutPanelParser


git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/2.1@9302 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/attributeparsers/LengthAttributeParser.java b/user/src/com/google/gwt/uibinder/attributeparsers/LengthAttributeParser.java
index 2e788dd..3d4bae7 100644
--- a/user/src/com/google/gwt/uibinder/attributeparsers/LengthAttributeParser.java
+++ b/user/src/com/google/gwt/uibinder/attributeparsers/LengthAttributeParser.java
@@ -33,7 +33,7 @@
   // This regular expression matches CSS length patterns of the form
   // (value)(unit), where the two may be separated by whitespace. Either part
   // can be a {class.method} expression.
-  private static final Pattern pattern = Pattern.compile("((?:\\{[\\w\\.]+\\})|[\\d\\.]+)\\s*(\\{?[\\w\\.\\%]*\\}?)?");
+  private static final Pattern pattern = Pattern.compile("((?:\\{[\\w\\.]+\\})|[\\+\\-]?[\\d\\.]+)\\s*(\\{?[\\w\\.\\%]*\\}?)?");
 
   private final MortalLogger logger;
   private final DoubleAttributeParser doubleParser;
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/LayoutPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/LayoutPanelParser.java
index b816847..a75209d 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/LayoutPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/LayoutPanelParser.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2009 Google Inc.
- *
+ * 
  * Licensed 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
@@ -27,6 +27,8 @@
 
   private static final String ERR_PAIRING = "'%s' must be paired with '%s' or '%s'.";
   private static final String ERR_TOO_MANY = "There are too many %s constraints.";
+  private static final String ERR_NEGATIVE_WIDTH = "Attribute 'width' can not be negative.";
+  private static final String ERR_NEGATIVE_HEIGHT = "Attribute 'height' can not be negative.";
   private static final String LAYER = "layer";
 
   public void parse(XMLElement elem, String fieldName, JClassType type,
@@ -41,14 +43,16 @@
       }
 
       // Get the child widget element.
-      String childFieldName = writer.parseElementToField(
-          layerElem.consumeSingleChildElement());
+      String childFieldName = writer.parseElementToField(layerElem.consumeSingleChildElement());
       writer.addStatement("%1$s.add(%2$s);", fieldName, childFieldName);
 
       // Parse the horizontal layout constraints.
       String left = layerElem.consumeLengthAttribute("left");
       String right = layerElem.consumeLengthAttribute("right");
       String width = layerElem.consumeLengthAttribute("width");
+      if (isNegative(width)) {
+        writer.die(layerElem, ERR_NEGATIVE_WIDTH);
+      }
 
       if (left != null) {
         if (right != null) {
@@ -76,6 +80,9 @@
       String top = layerElem.consumeLengthAttribute("top");
       String bottom = layerElem.consumeLengthAttribute("bottom");
       String height = layerElem.consumeLengthAttribute("height");
+      if (isNegative(height)) {
+        writer.die(layerElem, ERR_NEGATIVE_HEIGHT);
+      }
 
       if (top != null) {
         if (bottom != null) {
@@ -111,4 +118,17 @@
     return parent.getNamespaceUri().equals(child.getNamespaceUri())
         && type.equals(child.getLocalName());
   }
+
+  /**
+   * @return <code>true</code> if in given <code>value, unit</code> expression
+   *         <code>value</code> part is obviously negative.
+   */
+  private boolean isNegative(String expression) {
+    if (expression != null && expression.length() >= 2
+        && expression.charAt(0) == '-') {
+      char secondChar = expression.charAt(1);
+      return Character.isDigit(secondChar);
+    }
+    return false;
+  }
 }
diff --git a/user/test/com/google/gwt/uibinder/attributeparsers/LengthAttributeParserTest.java b/user/test/com/google/gwt/uibinder/attributeparsers/LengthAttributeParserTest.java
index b6e6407..ed2d73b 100644
--- a/user/test/com/google/gwt/uibinder/attributeparsers/LengthAttributeParserTest.java
+++ b/user/test/com/google/gwt/uibinder/attributeparsers/LengthAttributeParserTest.java
@@ -83,6 +83,8 @@
     assertEquals(lengthString("1", "EX"), parser.parse("1EX"));
 
     assertEquals(lengthString("2.5", "EM"), parser.parse("2.5em"));
+    assertEquals(lengthString("+1", "EM"), parser.parse("+1em"));
+    assertEquals(lengthString("-1", "EM"), parser.parse("-1em"));
 
     assertEquals(lengthString("1", "EM"), parser.parse("1 em"));
 
diff --git a/user/test/com/google/gwt/uibinder/elementparsers/LayoutPanelParserTest.java b/user/test/com/google/gwt/uibinder/elementparsers/LayoutPanelParserTest.java
index 5355f70..88fe292 100644
--- a/user/test/com/google/gwt/uibinder/elementparsers/LayoutPanelParserTest.java
+++ b/user/test/com/google/gwt/uibinder/elementparsers/LayoutPanelParserTest.java
@@ -21,7 +21,6 @@
 
 import org.xml.sax.SAXException;
 
-import java.io.IOException;
 import java.util.Iterator;
 
 /**
@@ -39,7 +38,7 @@
     tester = new ElementParserTester(PARSED_TYPE, new LayoutPanelParser());
   }
 
-  public void testBadChild() throws SAXException, IOException {
+  public void testBadChild() throws SAXException {
     StringBuffer b = new StringBuffer();
     b.append("<g:LayoutPanel>");
     b.append("  <g:blah/>");
@@ -54,7 +53,7 @@
     }
   }
 
-  public void testBadValue() throws SAXException, IOException {
+  public void testBadValue() throws SAXException {
     StringBuffer b = new StringBuffer();
     b.append("<g:LayoutPanel>");
     b.append("  <g:layer left='goosnarg'><g:HTML/></g:layer>");
@@ -69,8 +68,7 @@
     }
   }
 
-  public void testHappy() throws UnableToCompleteException, SAXException,
-      IOException {
+  public void testHappy() throws UnableToCompleteException, SAXException {
     StringBuffer b = new StringBuffer();
     b.append("<g:LayoutPanel>");
     b.append("  <g:layer>");
@@ -130,21 +128,69 @@
             + "com.google.gwt.dom.client.Style.Unit.EM, 50, foo.unit());" };
 
     tester.parse(b.toString());
+    
+    assertStatements(expected);
+  }
+  
+  public void testNegativeTopLeft() throws UnableToCompleteException, SAXException {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:LayoutPanel>");
+    b.append("  <g:layer left='-1em' width='2px' top='-3mm' height='4px'>");
+    b.append("    <g:Label/>");
+    b.append("  </g:layer>");
+    b.append("</g:LayoutPanel>");
+    
+    tester.parse(b.toString());
 
-    Iterator<String> i = tester.writer.statements.iterator();
-    for (String e : expected) {
-      assertEquals(e, i.next());
-    }
-    assertFalse(i.hasNext());
-    assertNull(tester.logger.died);
+    assertStatements("fieldName.add(<g:Label>);",
+        "fieldName.setWidgetLeftWidth(<g:Label>, "
+            + "-1, com.google.gwt.dom.client.Style.Unit.EM, "
+            + "2, com.google.gwt.dom.client.Style.Unit.PX);",
+        "fieldName.setWidgetTopHeight(<g:Label>, "
+            + "-3, com.google.gwt.dom.client.Style.Unit.MM, "
+            + "4, com.google.gwt.dom.client.Style.Unit.PX);");
   }
 
-  public void testLonelyBottom() throws SAXException, IOException {
+  public void testNegativeWidth() throws SAXException {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:LayoutPanel>");
+    b.append("  <g:layer left='1em' width='-2px'>");
+    b.append("    <g:Label/>");
+    b.append("  </g:layer>");
+    b.append("</g:LayoutPanel>");
+
+    try {
+      tester.parse(b.toString());
+      fail();
+    } catch (UnableToCompleteException e) {
+      String died = tester.logger.died;
+      assertTrue(died, died.contains("Attribute 'width' can not be negative"));
+    }
+  }
+  
+  public void testNegativeHeight() throws SAXException {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:LayoutPanel>");
+    b.append("  <g:layer top='1em' height='-2px'>");
+    b.append("    <g:Label/>");
+    b.append("  </g:layer>");
+    b.append("</g:LayoutPanel>");
+    
+    try {
+      tester.parse(b.toString());
+      fail();
+    } catch (UnableToCompleteException e) {
+      String died = tester.logger.died;
+      assertTrue(died, died.contains("Attribute 'height' can not be negative"));
+    }
+  }
+  
+  public void testLonelyBottom() throws SAXException {
     StringBuffer b = new StringBuffer();
     b.append("<g:LayoutPanel>");
     b.append("  <g:layer bottom='0'><g:HTML/></g:layer>");
     b.append("</g:LayoutPanel>");
-
+    
     try {
       tester.parse(b.toString());
       fail();
@@ -154,7 +200,7 @@
     }
   }
 
-  public void testLonelyLeft() throws SAXException, IOException {
+  public void testLonelyLeft() throws SAXException {
     StringBuffer b = new StringBuffer();
     b.append("<g:LayoutPanel>");
     b.append("  <g:layer left='0'><g:HTML/></g:layer>");
@@ -169,7 +215,7 @@
     }
   }
 
-  public void testLonelyRight() throws SAXException, IOException {
+  public void testLonelyRight() throws SAXException {
     StringBuffer b = new StringBuffer();
     b.append("<g:LayoutPanel>");
     b.append("  <g:layer right='0'><g:HTML/></g:layer>");
@@ -184,7 +230,7 @@
     }
   }
 
-  public void testLonelyTop() throws SAXException, IOException {
+  public void testLonelyTop() throws SAXException {
     StringBuffer b = new StringBuffer();
     b.append("<g:LayoutPanel>");
     b.append("  <g:layer top='0'><g:HTML/></g:layer>");
@@ -199,7 +245,7 @@
     }
   }
 
-  public void testOverConstrained() throws SAXException, IOException {
+  public void testOverConstrainedHorizontally() throws SAXException {
     StringBuffer b = new StringBuffer();
     b.append("<g:LayoutPanel>");
     b.append("  <g:layer left='0' width='0' right='0'><g:HTML/></g:layer>");
@@ -209,8 +255,32 @@
       tester.parse(b.toString());
       fail();
     } catch (UnableToCompleteException e) {
-      assertTrue("expect \"too many\" error",
-          tester.logger.died.contains("too many"));
+      String died = tester.logger.died;
+      assertTrue(died, died.contains("too many horizontal constraints"));
     }
   }
+  
+  public void testOverConstrainedVertically() throws SAXException {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:LayoutPanel>");
+    b.append("  <g:layer top='0' height='0' bottom='0'><g:HTML/></g:layer>");
+    b.append("</g:LayoutPanel>");
+    
+    try {
+      tester.parse(b.toString());
+      fail();
+    } catch (UnableToCompleteException e) {
+      String died = tester.logger.died;
+      assertTrue(died, died.contains("too many vertical constraints"));
+    }
+  }
+
+  private void assertStatements(String... expected) {
+    Iterator<String> i = tester.writer.statements.iterator();
+    for (String e : expected) {
+      assertEquals(e, i.next());
+    }
+    assertFalse(i.hasNext());
+    assertNull(tester.logger.died);
+  }
 }