1 | /* |
2 | * Copyright 2005-2006 The RbUtils Project |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | * |
16 | */ |
17 | |
18 | // $Id: FileGenerator.java,v 1.4 2008/07/01 07:43:45 moishi Exp $ |
19 | |
20 | package org.ktc.rbutils.rb.generation; |
21 | |
22 | import java.io.BufferedWriter; |
23 | import java.io.File; |
24 | import java.io.FileNotFoundException; |
25 | import java.io.FileOutputStream; |
26 | import java.io.IOException; |
27 | import java.io.OutputStreamWriter; |
28 | import java.io.Writer; |
29 | import java.text.MessageFormat; |
30 | import java.util.ArrayList; |
31 | import java.util.Collections; |
32 | import java.util.Date; |
33 | import java.util.Iterator; |
34 | import java.util.List; |
35 | import org.apache.commons.io.FilenameUtils; |
36 | import org.apache.commons.lang.StringEscapeUtils; |
37 | import org.apache.commons.lang.StringUtils; |
38 | import org.ktc.rbutils.RbUtilsHelper; |
39 | import org.ktc.rbutils.api.audit.AbstractFileProcessor; |
40 | import org.ktc.rbutils.api.file.FileTools; |
41 | import org.ktc.rbutils.api.file.ValidateFile; |
42 | import org.ktc.rbutils.api.i18n.LocaleUtils; |
43 | import org.ktc.rbutils.api.i18n.PropertiesRB; |
44 | import org.ktc.rbutils.rb.check.messages; |
45 | |
46 | /** |
47 | * Generates a Java RessourceBundle source class using a Properties file. |
48 | * <p> |
49 | * Package name resolution is proceeded using the relative path of the Properties file from a root |
50 | * directory. |
51 | * @see MainGenerator |
52 | * @since RbUtils 0.8.0 |
53 | * @version $Revision: 1.4 $ |
54 | * @author ktcguru |
55 | */ |
56 | public class FileGenerator extends AbstractFileProcessor { |
57 | // RFE Code - static parts of the code should be taken from a properties file |
58 | |
59 | /** String used for tabulation representation on code generation. */ |
60 | protected static final String TAB = " "; |
61 | /** Left padding for braces on code generation. */ |
62 | private static final String BRACES_PAD_LEFT = " "; |
63 | /** Right padding for braces on code generation. */ |
64 | private static final String BRACES_PAD_RIGHT = BRACES_PAD_LEFT; |
65 | /** Comma and 2 end of line characters. */ |
66 | private static final String COMMA_DBLENDOFLINE = ";\n\n"; |
67 | |
68 | // TODO Traduction - le message ne semble pas trop anglais |
69 | //TODO Traduction - ne pas mettre javatools en dur dans le code |
70 | /** |
71 | * String used for message formatting. This String represent the whole code of the generated |
72 | * class. |
73 | */ |
74 | private static final String FORMAT = "/*\n" + " * DO NOT EDIT!\n" |
75 | + " * Automaticly generated with RbUtils " + RbUtilsHelper.getVersion() + " on " |
76 | + new Date() |
77 | + "\n * Original source:\n" + " * " + TAB + "{0}\n" + " *\n" + " * RbUtils Home Page: " |
78 | + RbUtilsHelper.getHomePage() + "\n */\n" + "{1}" |
79 | + "{5}" |
80 | + "public class {2} extends {6} '{'\n" + "{3}\n" + TAB |
81 | + "private static Object[][] contents = '{'\n" + "{4}\n" + TAB + "}" + COMMA_DBLENDOFLINE |
82 | + TAB + "protected Object[][] getContents() '{'\n " + TAB + TAB + "return contents;\n" |
83 | + TAB + "}\n}\n"; |
84 | |
85 | /** Not compiled RB file. */ |
86 | private PropertiesRB properties; |
87 | /** Full path to the properties file. */ |
88 | private String propsFullFileName; |
89 | /** Package name of the generated ResourceBundle source class. */ |
90 | private String packageName; |
91 | /** File of the generated ResourceBundle source class. */ |
92 | private File javaRbFile; |
93 | |
94 | /** |
95 | * Instanciates a new <code>FileGenerator</code>. |
96 | * <p> |
97 | * <code>propsRootDir</code> and <code>genRootdir</code> must be directories that exist in |
98 | * the filesystem. <br> |
99 | * <code>propsFile</code> must be a file that exists in the filesystem. |
100 | * @param propsRootDir the root directory of the properties file used to resolve package name. |
101 | * @param propsFile the properties file used to generate the java file. |
102 | * @param genRootdir the target root directory where the java file will be put after package |
103 | * name resolution and file generation. |
104 | * @throws org.apache.commons.lang.NullArgumentException if a parameter is <code>null</code>. |
105 | * @throws java.io.FileNotFoundException if a parameter is a <code>File</code> that does not |
106 | * exist in the filesystem. |
107 | * @throws org.ktc.rbutils.api.file.NotDirectoryException if <code>propsRootDir</code> or |
108 | * <code>genRootdir</code> is not a directory in the filesystem. |
109 | * @throws org.ktc.rbutils.api.file.NotFileException if <code>propsFile</code> is not a file |
110 | * in the filesystem. |
111 | * @throws IOException if problems occured on Properties file load. |
112 | */ |
113 | public FileGenerator(final File propsRootDir, final File propsFile, final File genRootdir) |
114 | throws IOException |
115 | { |
116 | // Validate arguments |
117 | ValidateFile.isDirectory(propsRootDir); |
118 | ValidateFile.isFile(propsFile); |
119 | ValidateFile.isDirectory(genRootdir); |
120 | |
121 | // Load RB file into PropertiesRB |
122 | properties = new PropertiesRB(); |
123 | properties.load(propsFile); |
124 | |
125 | propsFullFileName = propsFile.getAbsolutePath(); |
126 | packageName = FileTools.inferPackageName(propsRootDir, propsFile); |
127 | javaRbFile = getJavaRbFile(propsRootDir, propsFile, genRootdir); |
128 | |
129 | // AbstractFileAuditer field initialization |
130 | className = getJavaRbClassName(propsFile); |
131 | fileLocale = properties.getLocale(); |
132 | fileName = FileTools.getStrippedFileName(propsRootDir, propsFile, true); |
133 | |
134 | // Set the resource class for messages |
135 | classResource = messages.class.getName(); |
136 | } |
137 | |
138 | //TODO Code - performGeneration() should be refactored into several methods |
139 | /** |
140 | * Performs the generation of the Java File. |
141 | * <p> |
142 | * The contents of the properties file is used to create the data of the java file. Then, the |
143 | * java file is created in the filesystem. |
144 | */ |
145 | protected void performGeneration() { |
146 | // Generation starts |
147 | fireProcessFileStarted(); |
148 | |
149 | // Get package line if it is not the default package |
150 | String packageString = StringUtils.EMPTY; |
151 | // if (packageName != null && !StringUtils.EMPTY.equals(packageName)) { |
152 | // packageName is never null |
153 | if (!StringUtils.EMPTY.equals(packageName)) { |
154 | // packageString = "package " + packageName + ";\n\n"; |
155 | packageString = "package " + packageName + COMMA_DBLENDOFLINE; |
156 | } |
157 | |
158 | // Is properties locale empty ? |
159 | final boolean isEmptyLocale = LocaleUtils.getEmptyLocale().equals(fileLocale); |
160 | |
161 | // Get the extended and the imported class |
162 | final String extendedClass; |
163 | final String importedClass; |
164 | if (isEmptyLocale) { |
165 | extendedClass = "ListResourceBundle"; |
166 | importedClass = "java.util.ListResourceBundle"; |
167 | } |
168 | else { |
169 | extendedClass = LocaleUtils.removeLocale(className); |
170 | importedClass = StringUtils.EMPTY; |
171 | } |
172 | |
173 | // TODO Code - should be done in the test above |
174 | // Get the import line |
175 | final String importLine; |
176 | if (StringUtils.EMPTY.equals(importedClass)) { |
177 | importLine = StringUtils.EMPTY; |
178 | } |
179 | else { |
180 | // importLine = "import " + importedClass + ";\n\n"; |
181 | importLine = "import " + importedClass + COMMA_DBLENDOFLINE; |
182 | } |
183 | |
184 | // Get constant fields and data (keys and values) |
185 | final List sortedKeys = new ArrayList(properties.keySet()); |
186 | Collections.sort(sortedKeys); |
187 | |
188 | final StringBuffer workingFields = new StringBuffer(200); |
189 | final StringBuffer workingData = new StringBuffer(200); |
190 | for (final Iterator iter = sortedKeys.iterator(); iter.hasNext();) { |
191 | final String key = (String) iter.next(); |
192 | final String value = (String) properties.get(key); |
193 | |
194 | final String currentField = escapeKey(key); |
195 | // Append constant field if locale is empty |
196 | if (isEmptyLocale) { |
197 | workingFields.append(TAB + "public static final String " + currentField + " = \"" |
198 | + key + "\";\n"); |
199 | } |
200 | // Append data |
201 | // Correction because of additionnal space after the BRACES_PAD_LEFT |
202 | // workingData.append(TAB + TAB + "{" + BRACES_PAD_RIGHT + currentField + ", \"" |
203 | // + escape(value) + "\"" + BRACES_PAD_LEFT + " }, \n"); |
204 | workingData.append(TAB + TAB + "{" + BRACES_PAD_RIGHT + currentField + ", \"" |
205 | + escape(value) + "\"" + BRACES_PAD_LEFT + "}, \n"); |
206 | } |
207 | |
208 | // Get the fields String to be added to the file |
209 | final String fields; |
210 | if (workingFields.length() > 0) { |
211 | fields = workingFields.toString(); |
212 | } |
213 | else { |
214 | fields = StringUtils.EMPTY; |
215 | } |
216 | // Suppress redondant coma on data and get the data String to be added to the file |
217 | // TODO Code - this is not necessary |
218 | // BUGS_2 Fix |
219 | // final String data = workingData.substring(0, workingData.lastIndexOf(",")); |
220 | // if workingData is empty, throws exception (occurs for empty files). Test length as for |
221 | // workingFields |
222 | // final String data = workingData.substring(0, workingData.toString().lastIndexOf(',')); |
223 | final String data; |
224 | if (workingData.length() > 0) { |
225 | data = workingData.substring(0, workingData.toString().lastIndexOf(',')); |
226 | } |
227 | else { |
228 | data = StringUtils.EMPTY; |
229 | } |
230 | |
231 | // Create java resourcebundle file and its parent's directory |
232 | try { |
233 | javaRbFile.getParentFile().mkdirs(); |
234 | javaRbFile.createNewFile(); |
235 | |
236 | // Generate java resourcebundle file |
237 | // RFE encoding of the generated java file |
238 | final Writer oswriter = new OutputStreamWriter(new FileOutputStream(javaRbFile), |
239 | "8859_1"); |
240 | final Writer buffwriter = new BufferedWriter(oswriter); |
241 | |
242 | final MessageFormat messageformat = new MessageFormat(FORMAT); |
243 | final Object[] formatParams = new Object[] {propsFullFileName, packageString, |
244 | className, fields, data, importLine, extendedClass}; |
245 | buffwriter.write(messageformat.format(formatParams)); |
246 | buffwriter.flush(); |
247 | buffwriter.close(); |
248 | } |
249 | catch (final Exception e) { |
250 | fireException(e); |
251 | } |
252 | |
253 | // Generation ends |
254 | fireProcessFileEnded(); |
255 | } |
256 | |
257 | // TODO Code - mettre la méthode private |
258 | /** |
259 | * Gets the name of the class generated from a Properties file. |
260 | * <p> |
261 | * This is the name of the file without extension. |
262 | * @param propsFile Properties file used for name resolution. |
263 | * @return the name of the class. |
264 | */ |
265 | protected static String getJavaRbClassName(final File propsFile) { |
266 | return FileTools.getNameWithoutExtension(propsFile); |
267 | } |
268 | |
269 | // TODO Code - mettre la méthode private |
270 | /** |
271 | * Gets the <code>File</code> of the Java ResourceBundle according to the relative path of a |
272 | * properties file from its root directory. |
273 | * <p> |
274 | * The returned <code>File</code> will be in a parent directory of <code>genRootdir</code>. |
275 | * It will have the same relative path to <code>genRootdir</code> that <code>propsFile</code> |
276 | * to <code>propsRootDir</code>. |
277 | * @param propsRootDir the root directory of the properties file used to resolve relative path. |
278 | * @param propsFile the properties file used to resolve relative path. |
279 | * @param genRootdir the target root directory where the java file will be put. |
280 | * @return the java resourcebundle <code>File</code>. |
281 | * @throws FileNotFoundException if one of the argument represent a file that does not exist in |
282 | * the filesystem. |
283 | */ |
284 | protected static File getJavaRbFile(final File propsRootDir, final File propsFile, |
285 | final File genRootdir) throws FileNotFoundException |
286 | { |
287 | final String strippedProps = FileTools.getStrippedFileName(propsRootDir, propsFile); |
288 | |
289 | final StringBuffer fileName = new StringBuffer(); |
290 | fileName.append(FilenameUtils.removeExtension(strippedProps)); |
291 | fileName.append(FileTools.EXTENSION_SEPARATOR); |
292 | fileName.append(FileTools.JAVA_EXTENSION); |
293 | |
294 | return new File(genRootdir, fileName.toString()); |
295 | } |
296 | |
297 | /** |
298 | * Escapes characters in order to get a <code>String</code> with only Unicode characters. |
299 | * @param theString the String to be escaped. |
300 | * @return the escaped String. |
301 | */ |
302 | protected static String escape(final String theString) { |
303 | // documenter le cas de \ seul : ce caractère est supprimé, utiliser \\ ? |
304 | // NON car dans les fichiers de propriétés c'est un caractère d'échappement pour passer à la |
305 | // ligne suivante |
306 | return StringEscapeUtils.escapeJava(theString); |
307 | // TODO Design - unescape qu'il faudrait utiliser (cf sun et props2java) ?? |
308 | } |
309 | |
310 | /** |
311 | * Escapes the dot character in the String argument into the underscore character. |
312 | * <p> |
313 | * This can be used on field generation in order to avoid compilation error (as the dot |
314 | * character is reserved for accessors). |
315 | * @param key the key to be escaped. |
316 | * @return the escape key; <code>null</code> if <code>key</code> is <code>null</code>. |
317 | */ |
318 | protected static String escapeKey(final String key) { |
319 | String theString = null; |
320 | if (key != null) { |
321 | theString = key.toUpperCase(LocaleUtils.getEmptyLocale()); |
322 | theString = theString.replace('.', '_'); |
323 | } |
324 | |
325 | // voir si ok en traitant l'expression réguliere : \p{Punct} |
326 | // ie : tout les caractèes de ponctuation |
327 | // Attention : ds le jdk a partir de la 1.4!!! |
328 | |
329 | // RFE Generateur - empecher les caractères interdits pour les variables |
330 | return theString; |
331 | } |
332 | |
333 | /** |
334 | * Returns a String which contains the number of tabulations specified as argument. |
335 | * @param tabNumber number of tabulations |
336 | * @return the String which represents the number of tabulations; the empty String is tabNumber |
337 | * is negative. |
338 | * @since RbUtils 0.9.2 |
339 | */ |
340 | protected static String tab(final int tabNumber) { |
341 | // TODO code - use this method |
342 | final StringBuffer tab = new StringBuffer(50); |
343 | for (int i = 0; i < tabNumber; i++) { |
344 | tab.append(TAB); |
345 | } |
346 | return tab.toString(); |
347 | } |
348 | } |