Add CLDR import for region names.

Patch by: jat
Review by: pdr

Review at http://gwt-code-reviews.appspot.com/1025801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9139 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/cldr-import/src/com/google/gwt/tools/cldr/CurrencyDataProcessor.java b/tools/cldr-import/src/com/google/gwt/tools/cldr/CurrencyDataProcessor.java
index 92fb6af..c28947f 100644
--- a/tools/cldr-import/src/com/google/gwt/tools/cldr/CurrencyDataProcessor.java
+++ b/tools/cldr-import/src/com/google/gwt/tools/cldr/CurrencyDataProcessor.java
@@ -59,6 +59,20 @@
     localeData.addCurrencyEntries("currency", cldrFactory, currencyFractions,
         defaultCurrencyFraction, stillInUse);
   }
+
+  @Override
+  protected void printHeader(PrintWriter pw) {
+    pw.println("# Do not edit - generated from Unicode CLDR data");
+    pw.println("#");
+    pw.println("# The key is an ISO4217 currency code, and the value is of the "
+        + "form:");
+    pw.println("#   display name|symbol|decimal digits|not-used-flag");
+    pw.println("# If a symbol is not supplied, the currency code will be used");
+    pw.println("# If # of decimal digits is omitted, 2 is used");
+    pw.println("# If a currency is not generally used, not-used-flag=1");
+    pw.println("# Trailing empty fields can be omitted");
+    pw.println();
+  }
   
   @Override
   protected void writeOutputFiles() throws IOException {
@@ -133,17 +147,4 @@
       }
     }
   }
-
-  private void printHeader(PrintWriter pw) {
-    pw.println("# Do not edit - generated from Unicode CLDR data");
-    pw.println("#");
-    pw.println("# The key is an ISO4217 currency code, and the value is of the "
-        + "form:");
-    pw.println("#   display name|symbol|decimal digits|not-used-flag");
-    pw.println("# If a symbol is not supplied, the currency code will be used");
-    pw.println("# If # of decimal digits is omitted, 2 is used");
-    pw.println("# If a currency is not generally used, not-used-flag=1");
-    pw.println("# Trailing empty fields can be omitted");
-    pw.println();
-  }
 }
diff --git a/tools/cldr-import/src/com/google/gwt/tools/cldr/DateTimeFormatInfoProcessor.java b/tools/cldr-import/src/com/google/gwt/tools/cldr/DateTimeFormatInfoProcessor.java
index 42edc54..ced118f 100644
--- a/tools/cldr-import/src/com/google/gwt/tools/cldr/DateTimeFormatInfoProcessor.java
+++ b/tools/cldr-import/src/com/google/gwt/tools/cldr/DateTimeFormatInfoProcessor.java
@@ -57,7 +57,6 @@
    */
   private static final Map<String, String> FORMATS;
 
-
   /**
    * Index of the formats, ordered by the method name. 
    */
@@ -600,30 +599,6 @@
         regionLanguageData, "//supplementalData/weekData/minDays", "minDays", "count");
   }
 
-  private void printHeader(PrintWriter pw) {
-    pw.println("/*");
-    pw.println(" * Copyright 2010 Google Inc.");
-    pw.println(" * ");
-    pw.println(" * Licensed under the Apache License, Version 2.0 (the "
-        + "\"License\"); you may not");
-    pw.println(" * use this file except in compliance with the License. You "
-        + "may obtain a copy of");
-    pw.println(" * the License at");
-    pw.println(" * ");
-    pw.println(" * http://www.apache.org/licenses/LICENSE-2.0");
-    pw.println(" * ");
-    pw.println(" * Unless required by applicable law or agreed to in writing, "
-        + "software");
-    pw.println(" * distributed under the License is distributed on an \"AS "
-        + "IS\" BASIS, WITHOUT");
-    pw.println(" * WARRANTIES OR CONDITIONS OF ANY KIND, either express or "
-        + "implied. See the");
-    pw.println(" * License for the specific language governing permissions and "
-        + "limitations under");
-    pw.println(" * the License.");
-    pw.println(" */");
-  }
-
   /**
    * Remove duplicates from period names.
    * 
diff --git a/tools/cldr-import/src/com/google/gwt/tools/cldr/GenerateGwtCldrData.java b/tools/cldr-import/src/com/google/gwt/tools/cldr/GenerateGwtCldrData.java
index cea2fe6..41199d9 100644
--- a/tools/cldr-import/src/com/google/gwt/tools/cldr/GenerateGwtCldrData.java
+++ b/tools/cldr-import/src/com/google/gwt/tools/cldr/GenerateGwtCldrData.java
@@ -49,6 +49,7 @@
         CurrencyDataProcessor.class,
         DateTimeFormatInfoProcessor.class,
         ListFormattingProcessor.class,
+        LocalizedNamesProcessor.class,
     };
   }
 
@@ -72,7 +73,7 @@
     Set<String> locales = cldrFactory.getAvailable();
     if (DEBUG) {
       locales = new HashSet<String>(Arrays.asList(new String[] {
-          "root", "en", "en_US", "en_CA", "ar", "ar_IQ"}));
+          "root", "en", "zh", "zh_Hans", "zh_Hant", "zh_CN", "zh_TW"}));
     }
     System.out.println("Processing " + locales.size() + " locales");
     File outputDir = new File(targetDir);
diff --git a/tools/cldr-import/src/com/google/gwt/tools/cldr/ListFormattingProcessor.java b/tools/cldr-import/src/com/google/gwt/tools/cldr/ListFormattingProcessor.java
index 91bea10..9713c40 100644
--- a/tools/cldr-import/src/com/google/gwt/tools/cldr/ListFormattingProcessor.java
+++ b/tools/cldr-import/src/com/google/gwt/tools/cldr/ListFormattingProcessor.java
@@ -36,7 +36,7 @@
 
   @Override
   protected void cleanupData() {
-    localeData.removeCompleteDuplicates();
+    localeData.removeCompleteDuplicates("list");
   }
 
   @Override
diff --git a/tools/cldr-import/src/com/google/gwt/tools/cldr/LocaleData.java b/tools/cldr-import/src/com/google/gwt/tools/cldr/LocaleData.java
index db3cfba..a90262f 100644
--- a/tools/cldr-import/src/com/google/gwt/tools/cldr/LocaleData.java
+++ b/tools/cldr-import/src/com/google/gwt/tools/cldr/LocaleData.java
@@ -56,20 +56,20 @@
 
     private final String code;
 
-    private String symbol;
-
-    private String displayName;
-
-    private String pattern;
+    private int decimalDigits;
 
     private String decimalSeparator;
 
+    private String displayName;
+
     private String groupingSeparator;
 
-    private int decimalDigits;
-
     private boolean inUse;
 
+    private String pattern;
+
+    private String symbol;
+
     public Currency(String code) {
       this.code = code;
     }
@@ -196,9 +196,12 @@
    */
   private class LocaleComparator implements Comparator<GwtLocale> {
     public int compare(GwtLocale a, GwtLocale b) {
-      int depthA = localeDepth.get(a);
-      int depthB = localeDepth.get(b);
-      int c = depthB - depthA;
+      Integer depthA = localeDepth.get(a);
+      Integer depthB = localeDepth.get(b);
+      int c = 0;
+      if (depthA != null && depthB != null) {
+        c = depthB - depthA;
+      }
       if (c == 0) {
         c = a.compareTo(b);
       }
@@ -210,8 +213,8 @@
    * Encapsulates the key for lookup values, comprising a locale and a category.
    */
   private static class MapKey {
-    private final GwtLocale locale;
     private final String category;
+    private final GwtLocale locale;
 
     public MapKey(String category, GwtLocale locale) {
       this.category = category;
@@ -264,17 +267,6 @@
   }
   
   /**
-   * @param factory 
-   * @param localeName
-   * @return GwtLocale instance for CLDR locale
-   */
-  public static GwtLocale getGwtLocale(GwtLocaleFactory factory,
-      String localeName) {
-    return "root".equals(localeName) ? factory.getDefault()
-        : factory.fromString(localeName).getCanonicalForm();
-  }
-  
-  /**
    * Get the value of a given category of territory data inherited by a locale.
    * 
    * @param locale the locale to search for
@@ -299,19 +291,19 @@
     }
     return null;
   }
-
-  private Map<MapKey, Map<String, String>> maps;
-
-  private final HashMap<GwtLocale, GwtLocale> inheritsFrom;
   
   private final Map<GwtLocale, String> allLocales;
 
-  private final Map<GwtLocale, Integer> localeDepth;
-
   private final GwtLocale defaultLocale;
 
+  private final HashMap<GwtLocale, GwtLocale> inheritsFrom;
+  
+  private final Map<GwtLocale, Integer> localeDepth;
+
   private final GwtLocaleFactory localeFactory;
 
+  private Map<MapKey, Map<String, String>> maps;
+
   /**
    * Construct a LocaleData object.
    * 
@@ -324,7 +316,7 @@
     defaultLocale = localeFactory.getDefault();
     allLocales = new HashMap<GwtLocale, String>();
     for (String localeName : localeNames) {
-      allLocales.put(getGwtLocale(localeFactory, localeName), localeName);
+      allLocales.put(getGwtLocale(localeName), localeName);
     }
     inheritsFrom = new HashMap<GwtLocale, GwtLocale>();
     buildInheritsFrom();
@@ -646,6 +638,15 @@
   }
 
   /**
+   * @param localeName
+   * @return GwtLocale instance for CLDR locale
+   */
+  public GwtLocale getGwtLocale(String localeName) {
+    return "root".equals(localeName) ? localeFactory.getDefault()
+        : localeFactory.fromString(localeName);
+  }
+
+  /**
    * @return all locales that have some data associated with them.
    */
   public Set<GwtLocale> getNonEmptyLocales() {
diff --git a/tools/cldr-import/src/com/google/gwt/tools/cldr/LocalizedNamesProcessor.java b/tools/cldr-import/src/com/google/gwt/tools/cldr/LocalizedNamesProcessor.java
new file mode 100644
index 0000000..dc5a3e0
--- /dev/null
+++ b/tools/cldr-import/src/com/google/gwt/tools/cldr/LocalizedNamesProcessor.java
@@ -0,0 +1,377 @@
+/*
+ * 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.tools.cldr;
+
+import com.google.gwt.i18n.shared.GwtLocale;
+import com.google.gwt.tools.cldr.RegionLanguageData.RegionPopulation;
+
+import org.unicode.cldr.util.CLDRFile.Factory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.CollationKey;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Extract localized names from CLDR data.
+ */
+public class LocalizedNamesProcessor extends Processor {
+
+  private static class IndexedName implements Comparable<IndexedName> {
+
+    private final int index;
+    private final CollationKey key;
+    
+    public IndexedName(Collator collator, int index, String value) {
+      this.index = index;
+      this.key = collator.getCollationKey(value);
+    }
+
+    public int compareTo(IndexedName o) {
+      return key.compareTo(o.key);
+    }
+
+    /**
+     * @return index of this name.
+     */
+    public int getIndex() {
+      return index;
+    }
+  }
+
+  /**
+   * Split a list of region codes into an array.
+   * 
+   * @param regionList comma-separated list of region codes
+   * @return array of region codes, null if none
+   */
+  private static String[] getRegionOrder(String regionList) {
+    String[] split = null;
+    if (regionList != null && regionList.length() > 0) {
+      split = regionList.split(",");
+    }
+    return split;
+  }
+
+  private final RegionLanguageData regionLanguageData;
+
+  public LocalizedNamesProcessor(File outputDir, Factory cldrFactory,
+      LocaleData localeData) {
+    super(outputDir, cldrFactory, localeData);
+    regionLanguageData = new RegionLanguageData(cldrFactory);
+  }
+
+  @Override
+  protected void cleanupData() {
+    localeData.copyLocaleData("en", "default", "territory", "languages",
+        "scripts", "variants");
+    // Generate a sort order before removing duplicates
+    for (GwtLocale locale : localeData.getNonEmptyLocales("territory")) {
+      // TODO(jat): deal with language population data that has a script
+      Map<String, String> map = localeData.getEntries("territory", locale);
+      List<String> countryCodes = new ArrayList<String>();
+      for (String regionCode : map.keySet()) {
+        // only include real country codes
+        if (!"ZZ".equals(regionCode) && regionCode.length() == 2) {
+          countryCodes.add(regionCode);
+        }
+      }
+      Locale javaLocale = new Locale(
+          locale.getLanguageNotNull(), locale.getRegionNotNull(),
+          locale.getVariantNotNull());
+      Collator collator = Collator.getInstance(javaLocale);
+      IndexedName[] names = new IndexedName[countryCodes.size()];
+      for (int i = 0; i < names.length; ++i) {
+        names[i] = new IndexedName(collator, i, map.get(countryCodes.get(i)));
+      }
+      Arrays.sort(names);
+      StringBuilder buf = new StringBuilder();
+      boolean first = true;
+      for (int i = 0; i < names.length; ++i) {
+        if (first) {
+          first = false;
+        } else {
+          buf.append(',');
+        }
+        buf.append(countryCodes.get(names[i].getIndex()));
+      }
+      localeData.addEntry("territory", locale, "!sortorder", buf.toString());
+    }
+    Set<String> locales = cldrFactory.getAvailableLanguages();
+    for (GwtLocale locale : localeData.getAllLocales()) {
+      Set<RegionPopulation> regions = getRegionsForLocale(locale);
+      StringBuilder buf = new StringBuilder();
+      if (!locale.isDefault()) {
+        int count = 0;
+        for (RegionPopulation region : regions) {
+          // only keep the first 10, and stop if there aren't many speakers
+          if (++count > 10 || region.getLiteratePopulation() < 3000000) {
+            break;
+          }
+          if (count > 1) {
+            buf.append(',');
+          }
+          buf.append(region.getRegion());
+        }
+      }
+      localeData.addEntry("territory", locale, "!likelyorder", buf.toString());
+    }
+    localeData.removeDuplicates("territory");
+    localeData.removeDuplicates("language");
+    localeData.removeDuplicates("script");
+    localeData.removeDuplicates("variant");
+  }
+
+  @Override
+  protected void loadData() throws IOException {
+    System.out.println("Loading data for localized names");
+    localeData.addEntries("territory", cldrFactory,
+        "//ldml/localeDisplayNames/territories", "territory", "type");
+    localeData.addEntries("language", cldrFactory,
+        "//ldml/localeDisplayNames/languages", "language", "type");
+    localeData.addEntries("script", cldrFactory,
+        "//ldml/localeDisplayNames/scripts", "script", "type");
+    localeData.addEntries("variant", cldrFactory,
+        "//ldml/localeDisplayNames/variants", "variant", "type");
+    localeData.addEntries("localePattern", cldrFactory,
+        "//ldml/localeDisplayNames/localePattern", "localePattern", "unused");
+    localeData.addEntries("localeSeparator", cldrFactory,
+        "//ldml/localeDisplayNames/localeSeparator", "localeSeparator",
+        "unused");
+  }
+
+  @Override
+  protected void writeOutputFiles() throws IOException {
+    for (GwtLocale locale : localeData.getNonEmptyLocales("territory")) {
+      Map<String, String> namesMap = localeData.getEntries("territory", locale);
+      List<String> regionCodesWithNames = new ArrayList<String>();
+      for (String regionCode : namesMap.keySet()) {
+        if (!regionCode.startsWith("!")) {
+          // skip entries which aren't actually region codes
+          regionCodesWithNames.add(regionCode);
+        }
+      }
+      String[] sortOrder = getRegionOrder(namesMap.get("!sortorder"));
+      String[] likelyOrder = getRegionOrder(namesMap.get("!likelyorder"));
+      if (regionCodesWithNames.isEmpty() && sortOrder == null
+          && likelyOrder == null) {
+        // nothing to do
+        return;
+      }
+      // sort for deterministic output
+      Collections.sort(regionCodesWithNames);
+      if (locale.isDefault()) {
+        generateDefaultLocale(namesMap, regionCodesWithNames, sortOrder,
+            likelyOrder);
+      }
+      generateLocale(locale, namesMap, regionCodesWithNames, sortOrder,
+          likelyOrder);
+    }
+  }
+
+  /**
+   * @param namesMap 
+   * @param regionCodesWithNames 
+   * @param sortOrder 
+   * @param likelyOrder 
+   */
+  private void generateDefaultLocale(Map<String, String> namesMap,
+      List<String> regionCodesWithNames, String[] sortOrder,
+      String[] likelyOrder) throws IOException {
+    PrintWriter pw = null;
+    try {
+      pw = createOutputFile("client/DefaultLocalizedNames.java");
+      printHeader(pw);
+      pw.println("package com.google.gwt.i18n.client;");
+      pw.println();
+      pw.println("// DO NOT EDIT - GENERATED FROM CLDR DATA");
+      pw.println();
+      pw.println("/**"); 
+      pw.println(" * Default LocalizedNames implementation.");   
+      pw.println(" */");
+      pw.print("public class DefaultLocalizedNames extends "
+          + "DefaultLocalizedNamesBase {");
+      if (likelyOrder != null) {
+        writeStringListMethod(pw, "loadLikelyRegionCodes", likelyOrder);
+      }
+      pw.println();
+      pw.println("  @Override");
+      pw.println("  protected void loadNameMap() {");
+      pw.println("    super.loadNameMap();");
+      for (String code : regionCodesWithNames) {
+        String name = namesMap.get(code);
+        if (name != null) {
+          pw.println("    namesMap.put(\"" + quote(code) + "\", \""
+              + quote(name) + "\");");
+        }
+      }
+      pw.println("  }");
+      if (sortOrder != null) {
+        writeStringListMethod(pw, "loadSortedRegionCodes", sortOrder);
+      }
+      pw.println("}");
+    } finally {
+      if (pw != null) {
+        pw.close();
+      }
+    }
+  }
+
+  /**
+   * @param locale
+   * @param likelyOrder 
+   * @param sortOrder 
+   * @param regionCodesWithNames 
+   * @param namesMap 
+   */
+  private void generateLocale(GwtLocale locale, Map<String, String> namesMap,
+      List<String> regionCodesWithNames, String[] sortOrder,
+      String[] likelyOrder) throws IOException {
+    PrintWriter pw = null;
+    try {
+      pw = createFile("LocalizedNamesImpl", "java", locale.getAsString());
+      printHeader(pw);
+      pw.println("package com.google.gwt.i18n.client.impl.cldr;");
+      pw.println();
+      if (!regionCodesWithNames.isEmpty()) {
+        pw.println("import com.google.gwt.core.client.JavaScriptObject;");
+        pw.println();
+      }
+      pw.println("// DO NOT EDIT - GENERATED FROM CLDR DATA");
+      pw.println();
+      pw.println("/**"); 
+      pw.println(" * Localized names for the \"" + locale + "\" locale.");   
+      pw.println(" */");
+      pw.print("public class LocalizedNamesImpl" + localeSuffix(locale)
+          + " extends ");
+      if (locale.isDefault()) {
+        pw.print("LocalizedNamesImplBase");
+      } else {
+        pw.print("LocalizedNamesImpl" + localeSuffix(localeData.inheritsFrom(
+            locale)));
+      }
+      pw.println(" {");
+      if (!locale.isDefault()) {
+        if (likelyOrder != null) {
+          writeStringListMethod(pw, "loadLikelyRegionCodes", likelyOrder);
+        }
+        if (sortOrder != null) {
+          writeStringListMethod(pw, "loadSortedRegionCodes", sortOrder);
+        }
+        if (!regionCodesWithNames.isEmpty()) {
+          pw.println();
+          pw.println("  @Override");
+          pw.println("  protected void loadNameMapJava() {");
+          pw.println("    super.loadNameMapJava();");
+          for (String code : regionCodesWithNames) {
+            String name = namesMap.get(code);
+            if (name != null && !name.equals(code)) {
+              pw.println("    namesMap.put(\"" + quote(code) + "\", \""
+                  + quote(name) + "\");");
+            }
+          }
+          pw.println("  }");
+          pw.println();
+          pw.println("  @Override");
+          pw.println("  protected JavaScriptObject loadNameMapNative() {");
+          pw.println("    return overrideMap(super.loadNameMapNative(), "
+              + "loadMyNameMap());");
+          pw.println("  }");
+          pw.println();
+          pw.println("  private native JavaScriptObject loadMyNameMap() /*-{");
+          generateNativeMap(pw, regionCodesWithNames, namesMap);
+          pw.println("  }-*/;");
+        }
+      } else if (!regionCodesWithNames.isEmpty()) {
+        pw.println();
+        pw.println("  @Override");
+        pw.println("  protected native JavaScriptObject loadNameMapNative() "
+            + "/*-{");
+        generateNativeMap(pw, regionCodesWithNames, namesMap);
+        pw.println("  }-*/;");
+      }
+      pw.println("}");
+    } finally {
+      if (pw != null) {
+        pw.close();
+      }
+    }
+  }
+
+  /**
+   * @param regionCodesWithNames
+   * @param namesMap
+   */
+  private void generateNativeMap(PrintWriter pw,
+      List<String> regionCodesWithNames, Map<String, String> namesMap) {
+    pw.println("    return {");
+    boolean firstLine = true;
+    for (String code : regionCodesWithNames) {
+      String name = namesMap.get(code);
+      if (name != null && !name.equals(code)) {
+        if (firstLine) {
+          firstLine = false;
+        } else {
+          pw.println(",");
+        }
+        pw.print("        \"" + quote(code) + "\": \"" + quote(name) + "\"");
+      }
+    }
+    pw.println();
+    pw.println("    };");
+  }
+
+  /**
+   * @param locale
+   * @return region populations speaking this language
+   */
+  private Set<RegionPopulation> getRegionsForLocale(GwtLocale locale) {
+    Set<RegionPopulation> retVal = regionLanguageData.getRegions(
+        locale.getLanguageNotNull() + "_" + locale.getScriptNotNull());
+    if (retVal.isEmpty()) {
+      retVal = regionLanguageData.getRegions(locale.getLanguageNotNull());
+    }
+    return retVal;
+  }
+
+  /**
+   * Generate a method which returns an array of string constants.
+   * 
+   * @param pw PrintWriter to write on
+   * @param methodName the name of the method to create
+   * @param values the list of string values to return.
+   */
+  private void writeStringListMethod(PrintWriter pw, String methodName,
+      String[] values) {
+    pw.println();
+    pw.println("  @Override");
+    pw.println("  public String[] " + methodName + "() {");
+    pw.println("    return new String[] {");
+    for (String code : values) {
+      pw.println("        \"" + Processor.quote(code) + "\",");
+    }
+    pw.println("    };");
+    pw.println("  }");
+  }
+}
diff --git a/tools/cldr-import/src/com/google/gwt/tools/cldr/Processor.java b/tools/cldr-import/src/com/google/gwt/tools/cldr/Processor.java
index 7ad1b75..78b4dd1 100644
--- a/tools/cldr-import/src/com/google/gwt/tools/cldr/Processor.java
+++ b/tools/cldr-import/src/com/google/gwt/tools/cldr/Processor.java
@@ -49,9 +49,17 @@
     return (locale.isDefault() ? "" : "_") + locale.getAsString();
   }
   
-  protected final Factory cldrFactory;
+  /**
+   * @param value
+   * @return value with all quotes escaped
+   */
+  protected static String quote(String value) {
+    return value.replace("\"", "\\\"");
+  }
 
-  protected final LocaleData localeData;  
+  protected final Factory cldrFactory;  
+
+  protected final LocaleData localeData;
 
   protected final File outputDir;
 
@@ -165,7 +173,7 @@
         pw.println("  @Override");
       }
       pw.println("  public String " + method + "() {");
-      pw.println("    return \"" + value.replace("\"", "\\\"") + "\";");
+      pw.println("    return \"" + quote(value) + "\";");
       pw.println("  }");
     }
   }
@@ -184,6 +192,30 @@
    */
   protected abstract void loadData() throws IOException;
 
+  protected void printHeader(PrintWriter pw) {
+    pw.println("/*");
+    pw.println(" * Copyright 2010 Google Inc.");
+    pw.println(" * ");
+    pw.println(" * Licensed under the Apache License, Version 2.0 (the "
+        + "\"License\"); you may not");
+    pw.println(" * use this file except in compliance with the License. You "
+        + "may obtain a copy of");
+    pw.println(" * the License at");
+    pw.println(" * ");
+    pw.println(" * http://www.apache.org/licenses/LICENSE-2.0");
+    pw.println(" * ");
+    pw.println(" * Unless required by applicable law or agreed to in writing, "
+        + "software");
+    pw.println(" * distributed under the License is distributed on an \"AS "
+        + "IS\" BASIS, WITHOUT");
+    pw.println(" * WARRANTIES OR CONDITIONS OF ANY KIND, either express or "
+        + "implied. See the");
+    pw.println(" * License for the specific language governing permissions and "
+        + "limitations under");
+    pw.println(" * the License.");
+    pw.println(" */");
+  }
+
   /**
    * Set whether method definitions should use @Override.
    *