1/* 2 * Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package compileproperties; 27 28import java.io.BufferedWriter; 29import java.io.File; 30import java.io.FileInputStream; 31import java.io.FileNotFoundException; 32import java.io.FileOutputStream; 33import java.io.IOException; 34import java.io.OutputStreamWriter; 35import java.io.Writer; 36import java.text.MessageFormat; 37import java.util.ArrayList; 38import java.util.Collections; 39import java.util.Iterator; 40import java.util.List; 41import java.util.Properties; 42 43/** Translates a .properties file into a .java file containing the 44 * definition of a java.util.Properties subclass which can then be 45 * compiled with javac. <P> 46 * 47 * Usage: java CompileProperties [path to .properties file] [path to .java file to be output] [super class] 48 * 49 * Infers the package by looking at the common suffix of the two 50 * inputs, eliminating "classes" from it. 51 * 52 * @author Scott Violet 53 * @author Kenneth Russell 54 */ 55 56public class CompileProperties { 57 58 public static void main(String[] args) { 59 CompileProperties cp = new CompileProperties(); 60 boolean ok = cp.run(args); 61 if ( !ok ) { 62 System.exit(1); 63 } 64 } 65 66 public static interface Log { 67 void info(String msg); 68 void verbose(String msg); 69 void error(String msg, Exception e); 70 } 71 72 private String propfiles[]; 73 private String outfiles[] ; 74 private String supers[] ; 75 private int compileCount = 0; 76 private boolean quiet = false; 77 public Log log; 78 79 public void setLog(Log log) { 80 this.log = log; 81 } 82 83 public boolean run(String[] args) { 84 if (log == null) { 85 log = new Log() { 86 public void error(String msg, Exception e) { 87 System.err.println("ERROR: CompileProperties: " + msg); 88 if ( e != null ) { 89 System.err.println("EXCEPTION: " + e.toString()); 90 e.printStackTrace(); 91 } 92 } 93 public void info(String msg) { 94 System.out.println(msg); 95 } 96 public void verbose(String msg) { 97 if (!quiet) 98 System.out.println(msg); 99 } 100 }; 101 } 102 103 boolean ok = true; 104 /* Original usage */ 105 if (args.length == 2 && args[0].charAt(0) != '-' ) { 106 ok = createFile(args[0], args[1], "java.util.ListResourceBundle"); 107 } else if (args.length == 3) { 108 ok = createFile(args[0], args[1], args[2]); 109 } else if (args.length == 0) { 110 usage(log); 111 ok = false; 112 } else { 113 /* New batch usage */ 114 ok = parseOptions(args); 115 if ( ok && compileCount == 0 ) { 116 log.error("options parsed but no files to compile", null); 117 ok = false; 118 } 119 /* Need at least one file. */ 120 if ( !ok ) { 121 usage(log); 122 } else { 123 /* Process files */ 124 for ( int i = 0; i < compileCount && ok ; i++ ) { 125 ok = createFile(propfiles[i], outfiles[i], supers[i]); 126 } 127 } 128 } 129 return ok; 130 } 131 132 private boolean parseOptions(String args[]) { 133 boolean ok = true; 134 if ( compileCount > 0 ) { 135 String new_propfiles[] = new String[compileCount + args.length]; 136 String new_outfiles[] = new String[compileCount + args.length]; 137 String new_supers[] = new String[compileCount + args.length]; 138 System.arraycopy(propfiles, 0, new_propfiles, 0, compileCount); 139 System.arraycopy(outfiles, 0, new_outfiles, 0, compileCount); 140 System.arraycopy(supers, 0, new_supers, 0, compileCount); 141 propfiles = new_propfiles; 142 outfiles = new_outfiles; 143 supers = new_supers; 144 } else { 145 propfiles = new String[args.length]; 146 outfiles = new String[args.length]; 147 supers = new String[args.length]; 148 } 149 150 for ( int i = 0; i < args.length ; i++ ) { 151 if ( "-compile".equals(args[i]) && i+3 < args.length ) { 152 propfiles[compileCount] = args[++i]; 153 outfiles[compileCount] = args[++i]; 154 supers[compileCount] = args[++i]; 155 compileCount++; 156 } else if ( "-optionsfile".equals(args[i]) && i+1 < args.length ) { 157 String filename = args[++i]; 158 FileInputStream finput = null; 159 byte contents[] = null; 160 try { 161 finput = new FileInputStream(filename); 162 int byteCount = finput.available(); 163 if ( byteCount <= 0 ) { 164 log.error("The -optionsfile file is empty", null); 165 ok = false; 166 } else { 167 contents = new byte[byteCount]; 168 int bytesRead = finput.read(contents); 169 if ( byteCount != bytesRead ) { 170 log.error("Cannot read all of -optionsfile file", null); 171 ok = false; 172 } 173 } 174 } catch ( IOException e ) { 175 log.error("cannot open " + filename, e); 176 ok = false; 177 } 178 if ( finput != null ) { 179 try { 180 finput.close(); 181 } catch ( IOException e ) { 182 ok = false; 183 log.error("cannot close " + filename, e); 184 } 185 } 186 if ( ok = true && contents != null ) { 187 String tokens[] = (new String(contents)).split("\\s+"); 188 if ( tokens.length > 0 ) { 189 ok = parseOptions(tokens); 190 } 191 } 192 if ( !ok ) { 193 break; 194 } 195 } else if ( "-quiet".equals(args[i]) ) { 196 quiet = true; 197 } else { 198 log.error("argument error", null); 199 ok = false; 200 } 201 } 202 return ok; 203 } 204 205 private boolean createFile(String propertiesPath, String outputPath, 206 String superClass) { 207 boolean ok = true; 208 log.verbose("parsing: " + propertiesPath); 209 Properties p = new Properties(); 210 try { 211 p.load(new FileInputStream(propertiesPath)); 212 } catch ( FileNotFoundException e ) { 213 ok = false; 214 log.error("Cannot find file " + propertiesPath, e); 215 } catch ( IOException e ) { 216 ok = false; 217 log.error("IO error on file " + propertiesPath, e); 218 } 219 if ( ok ) { 220 String packageName = inferPackageName(propertiesPath, outputPath); 221 log.verbose("inferred package name: " + packageName); 222 List<String> sortedKeys = new ArrayList<String>(); 223 for ( Object key : p.keySet() ) { 224 sortedKeys.add((String)key); 225 } 226 Collections.sort(sortedKeys); 227 Iterator<String> keys = sortedKeys.iterator(); 228 229 StringBuffer data = new StringBuffer(); 230 231 while (keys.hasNext()) { 232 String key = keys.next(); 233 data.append(" { \"" + escape(key) + "\", \"" + 234 escape((String)p.get(key)) + "\" },\n"); 235 } 236 237 // Get class name from java filename, not the properties filename. 238 // (zh_TW properties might be used to create zh_HK files) 239 File file = new File(outputPath); 240 String name = file.getName(); 241 int dotIndex = name.lastIndexOf('.'); 242 String className; 243 if (dotIndex == -1) { 244 className = name; 245 } else { 246 className = name.substring(0, dotIndex); 247 } 248 249 String packageString = ""; 250 if (packageName != null && !packageName.equals("")) { 251 packageString = "package " + packageName + ";\n\n"; 252 } 253 254 Writer writer = null; 255 try { 256 writer = new BufferedWriter( 257 new OutputStreamWriter(new FileOutputStream(outputPath), "8859_1")); 258 MessageFormat format = new MessageFormat(FORMAT); 259 writer.write(format.format(new Object[] { packageString, className, superClass, data })); 260 } catch ( IOException e ) { 261 ok = false; 262 log.error("IO error writing to file " + outputPath, e); 263 } 264 if ( writer != null ) { 265 try { 266 writer.flush(); 267 } catch ( IOException e ) { 268 ok = false; 269 log.error("IO error flush " + outputPath, e); 270 } 271 try { 272 writer.close(); 273 } catch ( IOException e ) { 274 ok = false; 275 log.error("IO error close " + outputPath, e); 276 } 277 } 278 log.verbose("wrote: " + outputPath); 279 } 280 return ok; 281 } 282 283 private static void usage(Log log) { 284 log.info("usage:"); 285 log.info(" java CompileProperties path_to_properties_file path_to_java_output_file [super_class]"); 286 log.info(" -OR-"); 287 log.info(" java CompileProperties {-compile path_to_properties_file path_to_java_output_file super_class} -or- -optionsfile filename"); 288 log.info(""); 289 log.info("Example:"); 290 log.info(" java CompileProperties -compile test.properties test.java java.util.ListResourceBundle"); 291 log.info(" java CompileProperties -optionsfile option_file"); 292 log.info("option_file contains: -compile test.properties test.java java.util.ListResourceBundle"); 293 } 294 295 private static String escape(String theString) { 296 // This is taken from Properties.saveConvert with changes for Java strings 297 int len = theString.length(); 298 StringBuffer outBuffer = new StringBuffer(len*2); 299 300 for(int x=0; x<len; x++) { 301 char aChar = theString.charAt(x); 302 switch(aChar) { 303 case '\\':outBuffer.append('\\'); outBuffer.append('\\'); 304 break; 305 case '\t':outBuffer.append('\\'); outBuffer.append('t'); 306 break; 307 case '\n':outBuffer.append('\\'); outBuffer.append('n'); 308 break; 309 case '\r':outBuffer.append('\\'); outBuffer.append('r'); 310 break; 311 case '\f':outBuffer.append('\\'); outBuffer.append('f'); 312 break; 313 default: 314 if ((aChar < 0x0020) || (aChar > 0x007e)) { 315 outBuffer.append('\\'); 316 outBuffer.append('u'); 317 outBuffer.append(toHex((aChar >> 12) & 0xF)); 318 outBuffer.append(toHex((aChar >> 8) & 0xF)); 319 outBuffer.append(toHex((aChar >> 4) & 0xF)); 320 outBuffer.append(toHex( aChar & 0xF)); 321 } else { 322 if (specialSaveChars.indexOf(aChar) != -1) { 323 outBuffer.append('\\'); 324 } 325 outBuffer.append(aChar); 326 } 327 } 328 } 329 return outBuffer.toString(); 330 } 331 332 private static String inferPackageName(String inputPath, String outputPath) { 333 // Normalize file names 334 inputPath = new File(inputPath).getPath(); 335 outputPath = new File(outputPath).getPath(); 336 // Split into components 337 String sep; 338 if (File.separatorChar == '\\') { 339 sep = "\\\\"; 340 } else { 341 sep = File.separator; 342 } 343 String[] inputs = inputPath.split(sep); 344 String[] outputs = outputPath.split(sep); 345 // Match common names, eliminating first "classes" entry from 346 // each if present 347 int inStart = 0; 348 int inEnd = inputs.length - 2; 349 int outEnd = outputs.length - 2; 350 int i = inEnd; 351 int j = outEnd; 352 while (i >= 0 && j >= 0) { 353 // If a dir contains a dot, it's not a valid package and most likely 354 // a module name. 355 if (!inputs[i].equals(outputs[j]) || 356 (inputs[i].equals("gensrc") && outputs[j].equals("gensrc")) || 357 (inputs[i].contains("."))) { 358 ++i; 359 ++j; 360 break; 361 } 362 --i; 363 --j; 364 } 365 String result; 366 if (i < 0 || j < 0 || i >= inEnd || j >= outEnd) { 367 result = ""; 368 } else { 369 inStart = i; 370 StringBuffer buf = new StringBuffer(); 371 for (i = inStart; i <= inEnd; i++) { 372 buf.append(inputs[i]); 373 if (i < inEnd) { 374 buf.append('.'); 375 } 376 } 377 result = buf.toString(); 378 } 379 return result; 380 } 381 382 private static final String FORMAT = 383 "{0}" + 384 "public final class {1} extends {2} '{'\n" + 385 " protected final Object[][] getContents() '{'\n" + 386 " return new Object[][] '{'\n" + 387 "{3}" + 388 " };\n" + 389 " }\n" + 390 "}\n"; 391 392 // This comes from Properties 393 private static char toHex(int nibble) { 394 return hexDigit[(nibble & 0xF)]; 395 } 396 397 // This comes from Properties 398 private static final char[] hexDigit = { 399 '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' 400 }; 401 402 // Note: different from that in Properties 403 private static final String specialSaveChars = "\""; 404} 405