001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.i18n.rebind.util;
017:
018: import com.google.gwt.dev.util.Util;
019: import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
020: import com.google.gwt.i18n.client.Localizable;
021: import com.google.gwt.i18n.tools.I18NSync;
022: import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
023: import com.google.gwt.user.rebind.SourceWriter;
024:
025: import org.apache.tapestry.util.text.LocalizedProperties;
026:
027: import java.io.File;
028: import java.io.FileInputStream;
029: import java.io.FileNotFoundException;
030: import java.io.FileOutputStream;
031: import java.io.IOException;
032: import java.io.InputStream;
033: import java.io.OutputStreamWriter;
034: import java.io.PrintWriter;
035: import java.io.Writer;
036: import java.util.ArrayList;
037: import java.util.HashSet;
038: import java.util.Iterator;
039: import java.util.List;
040: import java.util.Set;
041: import java.util.Map.Entry;
042: import java.util.regex.Pattern;
043:
044: /**
045: * Abstract base functionality for <code>MessagesInterfaceCreator</code> and
046: * <code>ConstantsInterfaceCreator</code>.
047: */
048: public abstract class AbstractLocalizableInterfaceCreator {
049: private static class RenameDuplicates extends ResourceKeyFormatter {
050: private Set<String> methodNames = new HashSet<String>();
051:
052: @Override
053: public String format(String key) {
054: if (methodNames.contains(key)) {
055: key += "_dup";
056: return format(key);
057: } else {
058: methodNames.add(key);
059: return key;
060: }
061: }
062: }
063:
064: private static class ReplaceBadChars extends ResourceKeyFormatter {
065: @Override
066: public String format(String key) {
067: return DEFAULT_CHARS.matcher(key).replaceAll("_");
068: }
069: }
070:
071: private abstract static class ResourceKeyFormatter {
072: public abstract String format(String key);
073: }
074:
075: private static Pattern DEFAULT_CHARS = Pattern.compile("[.-]");
076:
077: /**
078: * Composer for the current Constant.
079: */
080: protected SourceWriter composer;
081:
082: private List<ResourceKeyFormatter> formatters = new ArrayList<ResourceKeyFormatter>();
083:
084: private File resourceFile;
085:
086: private File sourceFile;
087:
088: private PrintWriter writer;
089:
090: /**
091: * Creates a new constants creator.
092: *
093: * @param className constant class to create
094: * @param packageName package to create it in
095: * @param resourceBundle resource bundle with value
096: * @param targetLocation
097: * @throws IOException
098: */
099: public AbstractLocalizableInterfaceCreator(String className,
100: String packageName, File resourceBundle,
101: File targetLocation,
102: Class<? extends Localizable> interfaceClass)
103: throws IOException {
104: setup(packageName, className, resourceBundle, targetLocation,
105: interfaceClass);
106: }
107:
108: /**
109: * Generate class.
110: *
111: * @throws FileNotFoundException
112: * @throws IOException
113: */
114: public void generate() throws FileNotFoundException, IOException {
115: try {
116: try {
117: // right now, only property files are legal
118: generateFromPropertiesFile();
119: } finally {
120: writer.close();
121: }
122: } catch (IOException e) {
123: sourceFile.delete();
124: throw e;
125: } catch (RuntimeException e) {
126: sourceFile.delete();
127: throw e;
128: }
129: }
130:
131: /**
132: * Create a String method declaration from a Dictionary/value pair.
133: *
134: * @param key Dictionary
135: * @param defaultValue default value
136: */
137: public void genSimpleMethodDecl(String key, String defaultValue) {
138: genMethodDecl("String", defaultValue, key, key);
139: }
140:
141: /**
142: * Create method args based upon the default value.
143: *
144: * @param defaultValue
145: */
146: protected abstract void genMethodArgs(String defaultValue);
147:
148: /**
149: * Returns the javaDocComment for the class.
150: *
151: * @param path path of class
152: * @return java doc comment
153: */
154: protected abstract String javaDocComment(String path);
155:
156: void generateFromPropertiesFile() throws IOException {
157: InputStream propStream = new FileInputStream(resourceFile);
158: LocalizedProperties p = new LocalizedProperties();
159: p.load(propStream, Util.DEFAULT_ENCODING);
160: addFormatters();
161: // TODO: Look for a generic version of Tapestry's LocalizedProperties class
162: Iterator<Entry<String, String>> elements = p.getPropertyMap()
163: .entrySet().iterator();
164: if (elements.hasNext() == false) {
165: throw new IllegalStateException(
166: "File '"
167: + resourceFile
168: + "' cannot be used to generate message classes, as it has no key/value pairs defined.");
169: }
170: while (elements.hasNext()) {
171: Entry<String, String> s = elements.next();
172: genSimpleMethodDecl(s.getKey(), s.getValue());
173: }
174: composer.commit(new PrintWriterTreeLogger());
175: }
176:
177: private void addFormatters() {
178: // For now, we completely control property key formatters.
179: formatters.add(new ReplaceBadChars());
180:
181: // Rename Duplicates must always come last.
182: formatters.add(new RenameDuplicates());
183: }
184:
185: private String formatKey(String key) {
186: for (ResourceKeyFormatter formatter : formatters) {
187: key = formatter.format(key);
188: }
189: if (Util.isValidJavaIdent(key) == false) {
190: System.err.println("Warning: " + key
191: + " is not a legitimate method name. " + sourceFile
192: + " will have compiler errors");
193: }
194: return key;
195: }
196:
197: private void genMethodDecl(String type, String defaultValue,
198: String key, String typeArg) {
199: composer.beginJavaDocComment();
200: composer.println("Translated \"" + defaultValue + "\".\n");
201: composer.println("@return translated \"" + defaultValue + "\"");
202: composer.print(I18NSync.ID + typeArg);
203: composer.endJavaDocComment();
204: key = formatKey(key);
205: composer.print(type + " " + key);
206: composer.print("(");
207: genMethodArgs(defaultValue);
208: composer.print(");\n");
209: }
210:
211: private void setup(String packageName, String className,
212: File resourceBundle, File targetLocation,
213: Class<? extends Localizable> interfaceClass)
214: throws IOException {
215: ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
216: packageName, className);
217: factory.makeInterface();
218: factory.setJavaDocCommentForClass(javaDocComment(resourceBundle
219: .getCanonicalPath().replace(File.separatorChar, '/')));
220: factory.addImplementedInterface(interfaceClass.getName());
221: FileOutputStream file = new FileOutputStream(targetLocation);
222: Writer underlying = new OutputStreamWriter(file,
223: Util.DEFAULT_ENCODING);
224: writer = new PrintWriter(underlying);
225: composer = factory.createSourceWriter(writer);
226: resourceFile = resourceBundle;
227: sourceFile = targetLocation;
228: }
229: }
|