| 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 | } |