| /* |
| * 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(); |
| } |
| } |