FIX: LookupMethodCreator create too large method #8611

add Tests for LookupMothodCreator to JRE Suite
Fix wrong JavaObjectType for primitive char in AbstractSourceCreator
Add Test and Suite for AbstractSourceCreator

Change-Id: Ia28cc75cc8b936e4ea118ff26527236b693f165f
diff --git a/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java
index fed873c..71f7260 100644
--- a/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java
@@ -1,16 +1,14 @@
 /*
  * Copyright 2008 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
+ * 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
- * License for the specific language governing permissions and limitations under
+ * 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 com.google.gwt.i18n.rebind;
@@ -31,23 +29,48 @@
  * Method creator to call the correct Map for the given Dictionary.
  */
 class LookupMethodCreator extends AbstractMethodCreator {
+
+  /**
+   * Used partition size if no one is specified.
+   * 
+   * Used in constructor without a partition size.
+   */
+  private static final int DEFAULT_PARTITIONS_SIZE = 500;
+
   private JType returnType;
 
+  private final int partitionsSize;
+
+  /**
+   * Constructor for <code>LookupMethodCreator</code>. The default partition size of 500 is used.
+   *
+   * @param classCreator parent class creator
+   * @param returnType associated return type
+   * 
+   * @see LookupMethodCreator#DEFAULT_PARTITIONS_SIZE
+   */
+  public LookupMethodCreator(AbstractGeneratorClassCreator classCreator, JType returnType) {
+    this(classCreator, returnType, DEFAULT_PARTITIONS_SIZE);
+    this.returnType = returnType;
+  }
+
   /**
    * Constructor for <code>LookupMethodCreator</code>.
    *
    * @param classCreator parent class creator
    * @param returnType associated return type
+   * @param partitionsSize max numbers of lookups per method.
    */
-  public LookupMethodCreator(AbstractGeneratorClassCreator classCreator,
-      JType returnType) {
+  public LookupMethodCreator(AbstractGeneratorClassCreator classCreator, JType returnType,
+      int partitionsSize) {
     super(classCreator);
     this.returnType = returnType;
+    this.partitionsSize = partitionsSize;
   }
 
   @Override
-  public void createMethodFor(TreeLogger logger, JMethod targetMethod,
-      String key, ResourceList resourceList, GwtLocale locale) {
+  public void createMethodFor(TreeLogger logger, JMethod targetMethod, String key,
+      ResourceList resourceList, GwtLocale locale) {
     createMethodFor(targetMethod);
   }
 
@@ -65,6 +88,15 @@
     return type;
   }
 
+  void printLookup(String methodName) {
+    String body = "if(arg0.equals(" + wrap(methodName) + ")) {";
+    println(body);
+    indent();
+    printFound(methodName);
+    outdent();
+    println("}");
+  }
+
   void createMethodFor(JMethod targetMethod) {
     String template = "{0} target = ({0}) cache.get(arg0);";
     String returnTypeName = getReturnTypeName();
@@ -76,24 +108,52 @@
     outdent();
     println("}");
     JMethod[] methods = ((ConstantsWithLookupImplCreator) currentCreator).allInterfaceMethods;
-    JType erasedType = returnType.getErasedType();
-    for (int i = 0; i < methods.length; i++) {
-      if (methods[i].getReturnType().getErasedType().equals(erasedType)
-          && methods[i] != targetMethod) {
-        String methodName = methods[i].getName();
-        String body = "if(arg0.equals(" + wrap(methodName) + ")) {";
-        println(body);
-        indent();
-        printFound(methodName);
-        outdent();
-        println("}");
-      }
+
+    final int partitions = (methods.length / partitionsSize) + 1;
+    println(returnTypeName + " tmp;");
+    for (int i = 0; i < partitions; i++) {
+      println("tmp = " + targetMethod.getName() + i + "(arg0);");
+      println("if (tmp != null) {");
+      indent();
+      println("return tmp;");
+      outdent();
+      println("}");
     }
+
     String format = "throw new java.util.MissingResourceException(\"Cannot find constant ''\" +"
         + "{0} + \"''; expecting a method name\", \"{1}\", {0});";
-    String result = MessageFormat.format(format, "arg0",
-        this.currentCreator.getTarget().getQualifiedSourceName());
+    String result = MessageFormat.format(format, "arg0", this.currentCreator.getTarget()
+        .getQualifiedSourceName());
     println(result);
+    outdent();
+    println("}");
+
+    println("");
+
+    final String argument0Type = targetMethod.getParameterTypes()[0].getQualifiedSourceName();
+    for (int p = 0; p < partitions; p++) {
+      final String templateNewMethod = "private {0} {1}{2}({3} arg0) '{";
+      final String header = MessageFormat.format(templateNewMethod, new Object[] {
+          returnTypeName, targetMethod.getName(), p, argument0Type});
+      println(header);
+      indent();
+      final JType erasedType = returnType.getErasedType();
+      for (int i = 0 + p * partitionsSize; i < methods.length && i < (p + 1)
+          * partitionsSize; i++) {
+        final JMethod method = methods[i];
+        if (method.getReturnType().getErasedType().equals(erasedType) && method != targetMethod) {
+          String methodName = method.getName();
+          printLookup(methodName);
+        }
+      }
+
+      println("return null;");
+      if (p < partitions - 1) {
+        outdent();
+        println("}");
+        println("");
+      }
+    }
   }
 
   void printFound(String methodName) {
diff --git a/user/src/com/google/gwt/user/rebind/AbstractSourceCreator.java b/user/src/com/google/gwt/user/rebind/AbstractSourceCreator.java
index 5f5e31f..ebe7fa3 100644
--- a/user/src/com/google/gwt/user/rebind/AbstractSourceCreator.java
+++ b/user/src/com/google/gwt/user/rebind/AbstractSourceCreator.java
@@ -82,6 +82,8 @@
   protected static String getJavaObjectTypeFor(JPrimitiveType type) {
     if (type == JPrimitiveType.INT) {
       return "Integer";
+    } else if (type == JPrimitiveType.CHAR){
+      return "Character";
     } else {
       String s = type.getSimpleSourceName();
       return s.substring(0, 1).toUpperCase(Locale.ROOT) + s.substring(1);
diff --git a/user/test/com/google/gwt/i18n/I18NJreSuite.java b/user/test/com/google/gwt/i18n/I18NJreSuite.java
index cf5d875..e5f5565 100644
--- a/user/test/com/google/gwt/i18n/I18NJreSuite.java
+++ b/user/test/com/google/gwt/i18n/I18NJreSuite.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.i18n.rebind.LocaleUtilsTest;
 import com.google.gwt.i18n.rebind.LocalizableGeneratorTest;
+import com.google.gwt.i18n.rebind.LookupMethodCreatorTest;
 import com.google.gwt.i18n.server.GwtLocaleTest;
 import com.google.gwt.i18n.server.MessageFormatParserTest;
 import com.google.gwt.i18n.server.PropertyCatalogFactoryTest;
@@ -28,6 +29,7 @@
 import com.google.gwt.i18n.shared.FirstStrongDirectionEstimatorTest;
 import com.google.gwt.i18n.shared.WordCountDirectionEstimatorTest;
 
+import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
 import junit.framework.TestSuite;
 
@@ -59,6 +61,7 @@
      */
     // suite.addTestSuite(TypeOracleMessageTest.class);
     suite.addTestSuite(WordCountDirectionEstimatorTest.class);
+    suite.addTest(new JUnit4TestAdapter(LookupMethodCreatorTest.class));
     // $JUnit-END$
 
     return suite;
diff --git a/user/test/com/google/gwt/i18n/rebind/LookupMethodCreatorTest.java b/user/test/com/google/gwt/i18n/rebind/LookupMethodCreatorTest.java
new file mode 100644
index 0000000..c9708eb
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/rebind/LookupMethodCreatorTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2017 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 License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.i18n.rebind;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.core.ext.typeinfo.TypeOracleException;
+import com.google.gwt.dev.javac.TypeOracleTestingUtils;
+import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
+import com.google.gwt.dev.shell.FailErrorLogger;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.google.gwt.user.rebind.StringSourceWriter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for {@link LookupMethodCreator}.
+ */
+public class LookupMethodCreatorTest {
+
+  private TreeLogger logger = new FailErrorLogger();
+
+  private LookupMethodCreator underTest;
+
+  private SourceWriter sw = new StringSourceWriter();
+
+  private TypeOracle oracle;
+
+  private JMethod method;
+
+  private static final MockJavaResource SINGLE_ENTRY_MESSAGES = new MockJavaResource(
+      "foo.SingleEntryMessage") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;\n");
+      code.append("public interface SingleEntryMessage extends foo.Lookup {\n");
+      code.append(" String singleEntry();\n");
+      code.append("}");
+      return code;
+    }
+  };
+
+  private static final MockJavaResource FOUR_ENTRY_MESSAGES = new MockJavaResource(
+      "foo.FourEntryMessage") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;\n");
+      code.append("public interface FourEntryMessage extends foo.Lookup {\n");
+      code.append(" String first();\n");
+      code.append(" String second();\n");
+      code.append(" String third();\n");
+      code.append(" String fourth();\n");
+      code.append("}");
+      return code;
+    }
+  };
+
+  private static final MockJavaResource LOOKUP = new MockJavaResource("foo.Lookup") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;\n");
+      code.append("public interface Lookup {\n");
+      code.append(" String getString(String arg0);\n");
+      code.append("}");
+      return code;
+    }
+  };
+
+  private void initLookupMethodCreator(MockJavaResource resource, int partitionsSize) {
+    JClassType clazz = oracle.findType(resource.getTypeName());
+    ConstantsWithLookupImplCreator mockCreator;
+    try {
+      mockCreator = new ConstantsWithLookupImplCreator(logger, sw, clazz, mock(ResourceList.class),
+          oracle);
+
+      JType stringType = oracle.findType("java.lang.String");
+      method = oracle.findType(LOOKUP.getTypeName()).findMethod("getString", new JType[] {
+          stringType});
+      underTest = new LookupMethodCreator(mockCreator, stringType, partitionsSize);
+    } catch (UnableToCompleteException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  @Before
+  public void setupUp() throws TypeOracleException, UnableToCompleteException {
+    oracle = TypeOracleTestingUtils.buildStandardTypeOracleWith(logger, SINGLE_ENTRY_MESSAGES,
+        FOUR_ENTRY_MESSAGES, LOOKUP);
+    initLookupMethodCreator(SINGLE_ENTRY_MESSAGES, 3);
+  }
+
+  /**
+   * Test the "golden output".<br>
+   * In general, those tests are more trouble than they are worth but this will be a simple test for
+   * verify the general structure.
+   * 
+   * old version looks: <pre>
+   * java.lang.String target = (java.lang.String) cache.get(arg0);
+   * if (target != null) {
+   *   return target;
+   * }
+   * if(arg0.equals("singleEntry")) {
+   *   return singleEntry();
+   * }
+   * throw new java.util.MissingResourceException("Cannot find constant '" +arg0 + "'; expecting a method name", "foo.TestMessage", arg0);
+   * </pre>
+   *
+   * with first partition lookup it generates:
+   *
+   * <pre>
+   * java.lang.String target = (java.lang.String) cache.get(arg0);
+   * if (target != null) {
+   *   return target;
+   * }
+   * java.lang.String tmp;
+   * tmp = getString0(arg0);
+   * if (tmp != null) {
+   *   return tmp;
+   * }
+   * throw new java.util.MissingResourceException("Cannot find constant '" +arg0 + "'; expecting a method name", "foo.TestMessage", arg0);
+   * }
+   *
+   * public java.lang.String getString0(java.lang.String arg0) {
+   *   if(arg0.equals("singleEntry")) {
+   *     return singleEntry();
+   *   }
+   *   return null;
+   *
+   * </pre>
+   */
+  @Test
+  public void testCreateMethodForJMethodForSingleEntry() {
+    underTest.createMethodFor(method);
+
+    SourceWriter expected = new StringSourceWriter();
+    expected.println("java.lang.String target = (java.lang.String) cache.get(arg0);");
+    expected.println("if (target != null) {");
+    expected.indent();
+    expected.println("return target;");
+    expected.outdent();
+    expected.println("}");
+
+    expected.println("java.lang.String tmp;");
+    expected.println("tmp = getString0(arg0);");
+    expected.println("if (tmp != null) {");
+    expected.indent();
+    expected.println("return tmp;");
+    expected.outdent();
+    expected.println("}");
+
+    expected.println("throw new java.util.MissingResourceException("
+        + "\"Cannot find constant '\" +arg0 + \"'; expecting a method name\", \"foo.SingleEntryMessage\", arg0);");
+
+    // check partition method
+    assertTrue("No partition lookup created.", sw.toString().contains(
+        "java.lang.String getString0(java.lang.String arg0) {"));
+
+    expected.println("}");
+    expected.println();
+    expected.println("private java.lang.String getString0(java.lang.String arg0) {");
+    expected.indent();
+    expected.println("if(arg0.equals(\"singleEntry\")) {");
+    expected.indent();
+    expected.println("return singleEntry();");
+    expected.outdent();
+    expected.println("}");
+    expected.println("return null;");
+    expected.outdent();
+
+    assertEquals("Wrong source Lookup created.", expected.toString(), sw.toString());
+  }
+
+  @Test
+  public void testCreateMethodForJMethodForMultiMessageEntryCreateTwoPartitions() {
+    initLookupMethodCreator(FOUR_ENTRY_MESSAGES, 3);
+    underTest.createMethodFor(method);
+
+    String actual = sw.toString();
+
+    assertTrue("Missing partition lookup method (getString0).", actual.contains(
+        "java.lang.String getString0(java.lang.String arg0) {"));
+
+    assertTrue("Missing partition lookup method (getString1).", actual.contains(
+        "java.lang.String getString1(java.lang.String arg0) {"));
+  }
+
+  @Test
+  public void testPrintFound() {
+    underTest.printFound("callTest");
+
+    String returnStatement = sw.toString();
+    assertEquals("return callTest();\n", returnStatement);
+  }
+
+  @Test
+  public void testGetReturnTypeName() {
+    String returnType = underTest.getReturnTypeName();
+    assertEquals("java.lang.String", returnType);
+  }
+
+  @Test
+  public void testGetReturnTypeNameForPrimitveTypes() {
+    for (JPrimitiveType primitiveType : JPrimitiveType.values()) {
+      LookupMethodCreator primitiveMethodCreator = new LookupMethodCreator(null, primitiveType, 2);
+      String returnType = primitiveMethodCreator.getReturnTypeName();
+      String expectedType = primitiveType.getQualifiedBoxedSourceName().substring("java.lang."
+          .length());
+      assertEquals("Wrong Return Type for primitve type", expectedType, returnType);
+    }
+  }
+
+}
diff --git a/user/test/com/google/gwt/user/UserJreSuite.java b/user/test/com/google/gwt/user/UserJreSuite.java
new file mode 100644
index 0000000..d6b3369
--- /dev/null
+++ b/user/test/com/google/gwt/user/UserJreSuite.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010 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 License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user;
+
+import com.google.gwt.user.rebind.AbstractSourceCreatorTest;
+
+import junit.framework.JUnit4TestAdapter;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * User tests running as a regular JRE test.
+ */
+public class UserJreSuite {
+  public static Test suite() {
+    TestSuite suite = new TestSuite("Non-browser tests for com.google.gwt.user");
+
+    // $JUnit-BEGIN$
+    suite.addTest(new JUnit4TestAdapter(AbstractSourceCreatorTest.class));
+    // $JUnit-END$
+
+    return suite;
+  }
+
+}
diff --git a/user/test/com/google/gwt/user/rebind/AbstractSourceCreatorTest.java b/user/test/com/google/gwt/user/rebind/AbstractSourceCreatorTest.java
new file mode 100644
index 0000000..8a5b3c7
--- /dev/null
+++ b/user/test/com/google/gwt/user/rebind/AbstractSourceCreatorTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 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 License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.rebind;
+
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class AbstractSourceCreatorTest {
+
+  @Test
+  public void testGetJavaObjectTypeForPrimitveTypes() {
+    for (JPrimitiveType primitiveType : JPrimitiveType.values()) {
+      String returnType = AbstractSourceCreator.getJavaObjectTypeFor(primitiveType);
+      String expectedType = primitiveType.getQualifiedBoxedSourceName().substring("java.lang."
+          .length());
+      assertEquals("Wrong Return Type for primitve type", expectedType, returnType);
+    }
+  }
+
+}