blob: dab54392f8390d16b984112811de2bacd4c07336 [file] [log] [blame]
/*
* 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.datetimefmtcreator;
import com.google.gwt.i18n.client.constants.DateTimeConstantsImpl;
import com.google.gwt.i18n.rebind.LocaleUtils;
import com.google.gwt.i18n.shared.GwtLocale;
import com.google.gwt.i18n.shared.GwtLocaleFactory;
import com.ibm.icu.text.DateTimePatternGenerator;
import com.ibm.icu.util.ULocale;
import org.apache.tapestry.util.text.LocalizedProperties;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
* Generate implementations of DateTimeFormatInfoImpl for all the supported
* locales.
*/
public class DateTimeFormatCreator {
private static class DtfiGenerator {
private static void buildPatterns(GwtLocale locale, TreeMap<Key, String[]> properties) {
ULocale ulocale = new ULocale(ULocale.canonicalize(locale.getAsString()));
DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(ulocale);
for (Map.Entry<String, String> entry : patterns.entrySet()) {
properties.put(new Key(locale, "format" + entry.getKey()), new String[] {dtpg
.getBestPattern(entry.getValue())});
}
}
private static GwtLocale findEarliestAncestor(GwtLocale locale, Set<GwtLocale> set) {
if (set == null) {
return null;
}
for (GwtLocale search : locale.getInheritanceChain()) {
if (set.contains(search)) {
return search;
}
}
return null;
}
private static String quote(String value) {
return value.replaceAll("\"", "\\\\\"");
}
private static String[] split(String target) {
// We add an artificial end character to avoid the odd split() behavior
// that drops the last item if it is only whitespace.
target = target + "~";
// Do not split on escaped commas.
String[] args = target.split("(?<![\\\\]),");
// Now remove the artificial ending we added above.
// We have to do it before we escape and trim because otherwise
// the artificial trailing '~' would prevent the last item from being
// properly trimmed.
if (args.length > 0) {
int last = args.length - 1;
args[last] = args[last].substring(0, args[last].length() - 1);
}
for (int i = 0; i < args.length; i++) {
args[i] = args[i].replaceAll("\\\\,", ",").trim();
}
return args;
}
private File propDir;
private File src;
public DtfiGenerator(File src) {
this.src = src;
String packageName = DateTimeConstantsImpl.class.getPackage().getName();
propDir = new File(src, packageName.replaceAll("\\.", "/"));
if (!propDir.exists()) {
System.err.println("Can't find directory for " + packageName);
return;
}
}
public void generate() throws FileNotFoundException, IOException {
final Pattern dtcProps = Pattern.compile("DateTimeConstantsImpl(.*)\\.properties");
String[] propFiles = propDir.list(new FilenameFilter() {
public boolean accept(File dir, String name) {
return dtcProps.matcher(name).matches();
}
});
TreeMap<Key, String[]> properties = new TreeMap<Key, String[]>();
GwtLocaleFactory factory = LocaleUtils.getLocaleFactory();
collectPropertyData(propFiles, properties, factory);
Map<GwtLocale, Set<GwtLocale>> parents = removeInheritedValues(properties);
generateSources(properties, parents);
}
private void addLocaleParent(Map<GwtLocale, Set<GwtLocale>> parents, GwtLocale keyLocale,
GwtLocale parentLocale) {
Set<GwtLocale> parentSet = parents.get(keyLocale);
if (parentSet == null) {
parentSet = new HashSet<GwtLocale>();
parents.put(keyLocale, parentSet);
}
parentSet.add(parentLocale);
}
@SuppressWarnings("unchecked")
private void collectPropertyData(String[] propFiles, TreeMap<Key, String[]> properties,
GwtLocaleFactory factory) throws FileNotFoundException, IOException {
for (String propFile : propFiles) {
if (!propFile.startsWith("DateTimeConstantsImpl") || !propFile.endsWith(".properties")) {
continue;
}
int len = propFile.length();
String suffix = propFile.substring(21, len - 11);
if (suffix.startsWith("_")) {
suffix = suffix.substring(1);
}
GwtLocale locale = factory.fromString(suffix).getCanonicalForm();
File f = new File(propDir, propFile);
FileInputStream str = null;
try {
str = new FileInputStream(f);
LocalizedProperties props = new LocalizedProperties();
props.load(str);
Map<String, String> map = props.getPropertyMap();
for (Map.Entry<String, String> entry : map.entrySet()) {
String[] value = split(entry.getValue());
if ("dateFormats".equals(entry.getKey()) || "timeFormats".equals(entry.getKey())
|| "weekendRange".equals(entry.getKey())) {
// split these out into separate fields
for (int i = 0; i < value.length; ++i) {
Key key = new Key(locale, entry.getKey() + i);
properties.put(key, new String[] {value[i]});
}
} else {
Key key = new Key(locale, entry.getKey());
properties.put(key, value);
}
}
buildPatterns(locale, properties);
} finally {
if (str != null) {
str.close();
}
}
}
}
private PrintWriter createClassSource(String packageName, String className)
throws FileNotFoundException {
String path = packageName.replace('.', '/') + "/" + className + ".java";
File f = new File(src, path);
FileOutputStream ostr = new FileOutputStream(f);
PrintWriter out = new PrintWriter(ostr);
out.println("/*");
out.println(" * Copyright 2010 Google Inc.");
out.println(" * ");
out.println(" * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not");
out.println(" * use this file except in compliance with the License. You may obtain a copy of");
out.println(" * the License at");
out.println(" * ");
out.println(" * http://www.apache.org/licenses/LICENSE-2.0");
out.println(" *");
out.println(" * Unless required by applicable law or agreed to in writing, software");
out.println(" * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT");
out.println(" * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the");
out.println(" * License for the specific language governing permissions and limitations under");
out.println(" * the License.");
out.println(" */");
out.println("package " + packageName + ";");
out.println();
out.println("// DO NOT EDIT - GENERATED FROM CLDR DATA");
out.println();
return out;
}
private void generateAlias(GwtLocale locale, GwtLocale parent) throws IOException {
System.out.println("Generating alias " + locale);
String suffix;
if (parent.isDefault()) {
suffix = "";
} else {
suffix = "_" + parent.getAsString();
}
String packageName = "com.google.gwt.i18n.client.impl.cldr";
String className = "DateTimeFormatInfoImpl_" + locale.getAsString();
PrintWriter out = null;
try {
out = createClassSource(packageName, className);
out.println("/**");
out.println(" * Locale \"" + locale + "\" is an alias for \"" + parent + "\".");
out.println(" */");
out.println("public class " + className + " extends DateTimeFormatInfoImpl" + suffix + " {");
out.println("}");
} finally {
if (out != null) {
out.close();
}
}
}
private void generateLocale(GwtLocale locale, GwtLocale parent, Map<String, String[]> values)
throws IOException {
System.out.println("Generating locale " + locale);
boolean addOverrides = true;
PrintWriter out = null;
try {
if (locale.isDefault()) {
String packageName = "com.google.gwt.i18n.client";
String className = "DefaultDateTimeFormatInfo";
out = createClassSource(packageName, className);
out.println("/**");
out.println(" * Default implementation of DateTimeFormatInfo interface, using values from");
out.println(" * the CLDR root locale.");
out.println(" * <p>");
out.println(" * Users who need to create their own DateTimeFormatInfo implementation are");
out.println(" * encouraged to extend this class so their implementation won't break when");
out.println(" * new methods are added.");
out.println(" */");
out.println("public class DefaultDateTimeFormatInfo implements DateTimeFormatInfo {");
addOverrides = false;
} else {
String suffix;
if (parent.isDefault()) {
suffix = "";
} else {
suffix = "_" + parent.getAsString();
}
String packageName = "com.google.gwt.i18n.client.impl.cldr";
String className = "DateTimeFormatInfoImpl_" + locale.getAsString();
out = createClassSource(packageName, className);
out.println("/**");
out.println(" * Implementation of DateTimeFormatInfo for locale \"" + locale + "\".");
out.println(" */");
out.println("public class " + className + " extends DateTimeFormatInfoImpl" + suffix
+ " {");
}
Set<String> keySet = values.keySet();
String[] keys = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keys, new Comparator<String>() {
public int compare(String a, String b) {
String mappedA = a;
String mappedB = b;
FieldMapping field = fieldMap.get(a);
if (field != null) {
mappedA = field.methodName;
}
field = fieldMap.get(b);
if (field != null) {
mappedB = field.methodName;
}
return mappedA.compareTo(mappedB);
}
});
for (String key : keys) {
String[] value = values.get(key);
FieldMapping mapping = fieldMap.get(key);
Class<?> type = value.length > 1 ? String[].class : String.class;
String related = null;
String name = key;
if (mapping != null) {
name = mapping.methodName;
type = mapping.type;
related = mapping.related;
}
String[] relatedValue = values.get(related);
String relayMethod = null;
if (Arrays.equals(value, relatedValue)) {
relayMethod = fieldMap.get(related).methodName;
}
out.println();
if (addOverrides) {
out.println(" @Override");
}
out.println(" public " + type.getSimpleName() + " " + name + "() {");
out.print(" return ");
if (relayMethod != null) {
out.println(relayMethod + "();");
} else {
if (type.isArray()) {
out.println("new " + type.getSimpleName() + " { ");
out.print(" ");
}
boolean first = true;
for (String oneValue : value) {
if (!first) {
out.println(",");
out.print(" ");
}
if (type == int.class || type == int[].class) {
out.print(Integer.valueOf(oneValue) - 1);
} else {
out.print("\"" + quote(oneValue) + "\"");
}
first = false;
}
if (type.isArray()) {
out.println();
out.println(" };");
} else {
out.println(";");
}
}
out.println(" }");
}
if (locale.isDefault()) {
// TODO(jat): actually generate these from CLDR data
out.println();
out.println(" public String dateFormat() {");
out.println(" return dateFormatMedium();");
out.println(" }");
out.println();
out.println(" public String dateTime(String timePattern, String datePattern) {");
out.println(" return datePattern + \" \" + timePattern;");
out.println(" }");
out.println();
out.println(" public String dateTimeFull(String timePattern, String datePattern) {");
out.println(" return dateTime(timePattern, datePattern);");
out.println(" }");
out.println();
out.println(" public String dateTimeLong(String timePattern, String datePattern) {");
out.println(" return dateTime(timePattern, datePattern);");
out.println(" }");
out.println();
out.println(" public String dateTimeMedium(String timePattern, String datePattern) {");
out.println(" return dateTime(timePattern, datePattern);");
out.println(" }");
out.println();
out.println(" public String dateTimeShort(String timePattern, String datePattern) {");
out.println(" return dateTime(timePattern, datePattern);");
out.println(" }");
out.println();
out.println(" public String timeFormat() {");
out.println(" return timeFormatMedium();");
out.println(" }");
}
out.println("}");
} finally {
if (out != null) {
out.close();
}
}
}
private void generateSources(TreeMap<Key, String[]> properties,
Map<GwtLocale, Set<GwtLocale>> parents) throws IOException {
Set<GwtLocale> locales = new HashSet<GwtLocale>();
// process sorted locales/keys, generating each locale on change
GwtLocale lastLocale = null;
Map<String, String[]> thisLocale = new HashMap<String, String[]>();
for (Entry<Key, String[]> entry : properties.entrySet()) {
if (lastLocale != null && lastLocale != entry.getKey().locale) {
GwtLocale parent = findEarliestAncestor(lastLocale, parents.get(lastLocale));
generateLocale(lastLocale, parent, thisLocale);
thisLocale.clear();
lastLocale = null;
}
if (lastLocale == null) {
lastLocale = entry.getKey().locale;
locales.add(lastLocale);
}
thisLocale.put(entry.getKey().key, entry.getValue());
}
if (lastLocale != null) {
GwtLocale parent = findEarliestAncestor(lastLocale, parents.get(lastLocale));
generateLocale(lastLocale, parent, thisLocale);
}
Set<GwtLocale> seen = new HashSet<GwtLocale>(locales);
for (GwtLocale locale : locales) {
for (GwtLocale alias : locale.getAliases()) {
if (!seen.contains(alias)) {
seen.add(alias);
// generateAlias(alias, locale);
}
}
}
}
/**
* Check if a given entry within a locale is inherited from a parent.
*
* @param properties
* @param parents
* @param key
* @param value
* @return true if the value is the same as the first parent defining that
* value
*/
private boolean isInherited(TreeMap<Key, String[]> properties,
Map<GwtLocale, Set<GwtLocale>> parents, Key key, String[] value) {
GwtLocale keyLocale = key.locale;
if (keyLocale.isDefault()) {
// never delete entries from default
return false;
}
List<GwtLocale> list = keyLocale.getInheritanceChain();
String[] parent = null;
for (int i = 1; i < list.size(); ++i) {
Key parentKey = new Key(list.get(i), key.key);
parent = properties.get(parentKey);
if (parent != null) {
GwtLocale parentLocale = parentKey.locale;
addLocaleParent(parents, keyLocale, parentLocale);
break;
}
}
return Arrays.equals(value, parent);
}
/**
* Remove inherited values and return a map of inherited-from locales for
* each locale.
*
* @param properties
* @return inheritance map
*/
private Map<GwtLocale, Set<GwtLocale>> removeInheritedValues(TreeMap<Key, String[]> properties) {
// remove entries identical to a parent locale
Map<GwtLocale, Set<GwtLocale>> parents = new HashMap<GwtLocale, Set<GwtLocale>>();
Set<Entry<Key, String[]>> entrySet = properties.entrySet();
Iterator<Entry<Key, String[]>> it = entrySet.iterator();
while (it.hasNext()) {
Entry<Key, String[]> entry = it.next();
if (isInherited(properties, parents, entry.getKey(), entry.getValue())) {
it.remove();
}
}
return parents;
}
}
private static class FieldMapping {
public final String methodName;
public final Class<?> type;
public final String related;
public FieldMapping(String methodName, Class<?> type) {
this(methodName, type, null);
}
public FieldMapping(String methodName, Class<?> type, String related) {
this.methodName = methodName;
this.type = type;
this.related = related;
}
}
private static class Key implements Comparable<Key> {
public final GwtLocale locale;
public final String key;
public Key(GwtLocale locale, String key) {
this.locale = locale;
this.key = key;
}
public int compareTo(Key other) {
int c = locale.compareTo(other.locale);
if (c == 0) {
c = key.compareTo(other.key);
}
return c;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Key other = (Key) obj;
return locale.equals(other.locale) && key.equals(other.key);
}
@Override
public int hashCode() {
return locale.hashCode() * 31 + key.hashCode();
}
@Override
public String toString() {
return locale.toString() + "/" + key;
}
}
private static final Map<String, FieldMapping> fieldMap = new HashMap<String, FieldMapping>();
private static Map<String, String> patterns = new HashMap<String, String>();
static {
fieldMap.put("ampms", new FieldMapping("ampms", String[].class));
fieldMap.put("dateFormats0", new FieldMapping("dateFormatFull", String.class));
fieldMap.put("dateFormats1", new FieldMapping("dateFormatLong", String.class));
fieldMap.put("dateFormats2", new FieldMapping("dateFormatMedium", String.class));
fieldMap.put("dateFormats3", new FieldMapping("dateFormatShort", String.class));
fieldMap.put("timeFormats0", new FieldMapping("timeFormatFull", String.class));
fieldMap.put("timeFormats1", new FieldMapping("timeFormatLong", String.class));
fieldMap.put("timeFormats2", new FieldMapping("timeFormatMedium", String.class));
fieldMap.put("timeFormats3", new FieldMapping("timeFormatShort", String.class));
fieldMap.put("eraNames", new FieldMapping("erasFull", String[].class));
fieldMap.put("eras", new FieldMapping("erasShort", String[].class));
fieldMap.put("quarters", new FieldMapping("quartersFull", String[].class));
fieldMap.put("shortQuarters", new FieldMapping("quartersShort", String[].class));
fieldMap.put("firstDayOfTheWeek", new FieldMapping("firstDayOfTheWeek", Integer.class));
fieldMap.put("months", new FieldMapping("monthsFull", String[].class));
fieldMap.put("standaloneMonths", new FieldMapping("monthsFullStandalone", String[].class,
"months"));
fieldMap.put("narrowMonths", new FieldMapping("monthsNarrow", String[].class));
fieldMap.put("standaloneNarrowMonths", new FieldMapping("monthsNarrowStandalone",
String[].class, "narrowMonths"));
fieldMap.put("shortMonths", new FieldMapping("monthsShort", String[].class));
fieldMap.put("standaloneShortMonths", new FieldMapping("monthsShortStandalone", String[].class,
"shortMonths"));
fieldMap.put("weekendRange0", new FieldMapping("weekendStart", int.class));
fieldMap.put("weekendRange1", new FieldMapping("weekendEnd", int.class));
fieldMap.put("firstDayOfTheWeek", new FieldMapping("firstDayOfTheWeek", int.class));
fieldMap.put("weekdays", new FieldMapping("weekdaysFull", String[].class));
fieldMap.put("standaloneWeekdays", new FieldMapping("weekdaysFullStandalone", String[].class,
"weekdays"));
fieldMap.put("shortWeekdays", new FieldMapping("weekdaysShort", String[].class));
fieldMap.put("standaloneShortWeekdays", new FieldMapping("weekdaysShortStandalone",
String[].class, "shortWeekdays"));
fieldMap.put("narrowWeekdays", new FieldMapping("weekdaysNarrow", String[].class));
fieldMap.put("standaloneNarrowWeekdays", new FieldMapping("weekdaysNarrowStandalone",
String[].class, "narrowWeekdays"));
// patterns to use with DateTimePatternGenerator
patterns.put("Day", "d");
patterns.put("Hour12Minute", "hmm");
patterns.put("Hour12MinuteSecond", "hmmss");
patterns.put("Hour24Minute", "Hmm");
patterns.put("Hour24MinuteSecond", "Hmmss");
patterns.put("MinuteSecond", "mss");
patterns.put("MonthAbbrev", "MMM");
patterns.put("MonthAbbrevDay", "MMMd");
patterns.put("MonthFull", "MMMM");
patterns.put("MonthFullDay", "MMMMd");
patterns.put("MonthFullWeekdayDay", "MMMMEEEEd");
patterns.put("MonthNumDay", "Md");
patterns.put("Year", "y");
patterns.put("YearMonthAbbrev", "yMMM");
patterns.put("YearMonthAbbrevDay", "yMMMd");
patterns.put("YearMonthFull", "yMMMM");
patterns.put("YearMonthFullDay", "yMMMMd");
patterns.put("YearMonthNum", "yM");
patterns.put("YearMonthNumDay", "yMd");
patterns.put("YearMonthWeekdayDay", "yMMMEEEd");
patterns.put("YearQuarterFull", "yQQQQ");
patterns.put("YearQuarterShort", "yQ");
}
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
if (args.length != 1) {
System.err.println("Usage: " + DateTimeFormatCreator.class.getSimpleName() + " gwt-root-dir");
return;
}
File gwt = new File(args[0]);
File src = new File(gwt, "user/src");
if (!gwt.exists() || !src.exists()) {
System.err.println(args[0] + " doesn't appear to be a GWT root directory");
return;
}
new DtfiGenerator(src).generate();
}
}