001/* 002// $Id: //open/util/resgen/src/org/eigenbase/resgen/Util.java#6 $ 003// Package org.eigenbase.resgen is an i18n resource generator. 004// Copyright (C) 2005-2005 The Eigenbase Project 005// Copyright (C) 2005-2005 Disruptive Tech 006// Copyright (C) 2005-2005 LucidEra, Inc. 007// Portions Copyright (C) 2001-2005 Kana Software, Inc. and others. 008// 009// This library is free software; you can redistribute it and/or modify it 010// under the terms of the GNU Lesser General Public License as published by the 011// Free Software Foundation; either version 2 of the License, or (at your 012// option) any later version approved by The Eigenbase Project. 013// 014// This library is distributed in the hope that it will be useful, 015// but WITHOUT ANY WARRANTY; without even the implied warranty of 016// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017// GNU Lesser General Public License for more details. 018// 019// You should have received a copy of the GNU Lesser General Public License 020// along with this library; if not, write to the Free Software 021// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 022*/ 023 024package org.eigenbase.resgen; 025import org.eigenbase.xom.*; 026 027import java.io.*; 028import java.lang.reflect.InvocationTargetException; 029import java.lang.reflect.Method; 030import java.net.MalformedURLException; 031import java.net.URL; 032import java.util.ArrayList; 033import java.util.Locale; 034 035/** 036 * Miscellaneous utility methods for the <code>org.eigenbase.resgen</code> 037 * package, all them <code>static</code> and package-private. 038 * 039 * @author jhyde 040 * @since 3 December, 2001 041 * @version $Id: //open/util/resgen/src/org/eigenbase/resgen/Util.java#6 $ 042 **/ 043abstract class Util { 044 045 private static final Throwable[] emptyThrowableArray = new Throwable[0]; 046 047 /** loads URL into Document and returns set of resources **/ 048 static ResourceDef.ResourceBundle load(URL url) 049 throws IOException 050 { 051 return load(url.openStream()); 052 } 053 054 /** loads InputStream and returns set of resources **/ 055 static ResourceDef.ResourceBundle load(InputStream inStream) 056 throws IOException 057 { 058 try { 059 Parser parser = XOMUtil.createDefaultParser(); 060 DOMWrapper def = parser.parse(inStream); 061 ResourceDef.ResourceBundle xmlResourceList = new 062 ResourceDef.ResourceBundle(def); 063 return xmlResourceList; 064 } catch (XOMException err) { 065 throw new IOException(err.toString()); 066 } 067 } 068 069 /** 070 * Left-justify a block of text. Line breaks are preserved, but long lines 071 * are broken. 072 * 073 * @param pw where to output the formatted text 074 * @param text the text to be written 075 * @param linePrefix a string to prepend to each output line 076 * @param lineSuffix a string to append to each output line 077 * @param maxTextPerLine the maximum number of characters to place on 078 * each line, not counting the prefix and suffix. If this is -1, 079 * never break lines. 080 **/ 081 static void fillText( 082 PrintWriter pw, String text, String linePrefix, String lineSuffix, 083 int maxTextPerLine) 084 { 085 int i = 0; 086 for (;;) { 087 int end = text.length(); 088 if (end <= i) { 089 // Nothing left. We're done. 090 break; 091 } 092 093 if (i > 0) { 094 // End the previous line and start another. 095 pw.println(lineSuffix); 096 pw.print(linePrefix); 097 } 098 099 int nextCR = text.indexOf("\r", i); 100 if (nextCR >= 0 && nextCR < end) { 101 end = nextCR; 102 } 103 int nextLF = text.indexOf("\n", i); 104 if (nextLF >= 0 && nextLF < end) { 105 end = nextLF; 106 } 107 108 if (maxTextPerLine > 0 && i + maxTextPerLine <= end) { 109 // More than a line left. Break at the last space before the 110 // line limit. 111 end = text.lastIndexOf(" ",i + maxTextPerLine); 112 if (end < i) { 113 // No space exists before the line limit; look beyond it. 114 end = text.indexOf(" ",i); 115 if (end < 0) { 116 // No space anywhere in the line. Take the whole line. 117 end = text.length(); 118 } 119 } 120 } 121 122 pw.print(text.substring(i, end)); 123 124 // The line is short enough. Print it, and find where the next one 125 // starts. 126 i = end; 127 while (i < text.length() && 128 (text.charAt(i) == ' ' || 129 text.charAt(i) == '\r' || 130 text.charAt(i) == '\n')) { 131 i++; 132 } 133 } 134 } 135 136 static URL stringToUrl(String strFile) throws IOException 137 { 138 try { 139 File f = new File(strFile); 140 return convertPathToURL(f); 141 } catch (Throwable err) { 142 throw new IOException(err.toString()); 143 } 144 } 145 146 /** 147 * Creates a file-protocol URL for the given filename. 148 **/ 149 static URL convertPathToURL(File file) 150 { 151 try { 152 String path = file.getAbsolutePath(); 153 // This is a bunch of weird code that is required to 154 // make a valid URL on the Windows platform, due 155 // to inconsistencies in what getAbsolutePath returns. 156 String fs = System.getProperty("file.separator"); 157 if (fs.length() == 1) 158 { 159 char sep = fs.charAt(0); 160 if (sep != '/') 161 path = path.replace(sep, '/'); 162 if (path.charAt(0) != '/') 163 path = '/' + path; 164 } 165 path = "file://" + path; 166 return new URL(path); 167 } catch (MalformedURLException e) { 168 throw new java.lang.Error(e.getMessage()); 169 } 170 } 171 172 static String formatError(String template, Object[] args) 173 { 174 String s = template; 175 for (int i = 0; i < args.length; i++) { 176 String arg = args[i].toString(); 177 s = replace(s, "%" + (i + 1), arg); 178 s = replace(s, "%i" + (i + 1), arg); 179 } 180 return s; 181 } 182 183 /** Returns <code>s</code> with every instance of <code>find</code> 184 * converted to <code>replace</code>. */ 185 static String replace(String s,String find,String replace) { 186 // let's be optimistic 187 int found = s.indexOf(find); 188 if (found == -1) { 189 return s; 190 } 191 StringBuffer sb = new StringBuffer(s.length()); 192 int start = 0; 193 for (;;) { 194 for (; start < found; start++) { 195 sb.append(s.charAt(start)); 196 } 197 if (found == s.length()) { 198 break; 199 } 200 sb.append(replace); 201 start += find.length(); 202 found = s.indexOf(find,start); 203 if (found == -1) { 204 found = s.length(); 205 } 206 } 207 return sb.toString(); 208 } 209 210 /** Return <code>val</code> in double-quotes, suitable as a string in a 211 * Java or JScript program. 212 * 213 * @param val the value 214 * @param nullMeansNull whether to print a null value as <code>null</code> 215 * (the default), as opposed to <code>""</code> 216 */ 217 static String quoteForJava(String val,boolean nullMeansNull) 218 { 219 if (val == null) { 220 return nullMeansNull ? "null" : ""; 221 } 222 String s0; 223 s0 = replace(val, "\\", "\\\\"); 224 s0 = replace(val, "\"", "\\\""); 225 s0 = replace(s0, "\n\r", "\\n"); 226 s0 = replace(s0, "\n", "\\n"); 227 s0 = replace(s0, "\r", "\\r"); 228 return "\"" + s0 + "\""; 229 } 230 231 static String quoteForJava(String val) 232 { 233 return quoteForJava(val,true); 234 } 235 236 /** 237 * Returns a string quoted so that it can appear in a resource file. 238 */ 239 static String quoteForProperties(String val) { 240 String s0; 241 s0 = replace(val, "\\", "\\\\"); 242// s0 = replace(val, "\"", "\\\""); 243// s0 = replace(s0, "'", "''"); 244 s0 = replace(s0, "\n\r", "\\n"); 245 s0 = replace(s0, "\n", "\\n"); 246 s0 = replace(s0, "\r", "\\r"); 247 s0 = replace(s0, "\t", "\\t"); 248 return s0; 249 } 250 251 static final char fileSep = System.getProperty("file.separator").charAt(0); 252 253 static String fileNameToClassName(String fileName, String suffix) { 254 String s = fileName; 255 s = removeSuffix(s, suffix); 256 s = s.replace(fileSep, '.'); 257 s = s.replace('/', '.'); 258 int score = s.indexOf('_'); 259 if (score >= 0) { 260 s = s.substring(0,score); 261 } 262 return s; 263 } 264 265 static String fileNameToCppClassName(String fileName, String suffix) { 266 String s = fileName; 267 s = removeSuffix(s, suffix); 268 269 int pos = s.lastIndexOf(fileSep); 270 if (pos >= 0) { 271 s = s.substring(pos + 1); 272 } 273 274 int score = s.indexOf('_'); 275 if (score >= 0) { 276 s = s.substring(0, score); 277 } 278 return s; 279 } 280 281 static String removeSuffix(String s, final String suffix) { 282 if (s.endsWith(suffix)) { 283 s = s.substring(0,s.length()-suffix.length()); 284 } 285 return s; 286 } 287 288 /** 289 * Given <code>happy/BirthdayResource_en_US.xml</code>, 290 * returns the locale "en_US". 291 */ 292 static Locale fileNameToLocale(String fileName, String suffix) { 293 String s = removeSuffix(fileName, suffix); 294 int score = s.indexOf('_'); 295 if (score <= 0) { 296 return null; 297 } else { 298 String localeName = s.substring(score + 1); 299 return parseLocale(localeName); 300 } 301 } 302 303 /** 304 * Parses 'localeName' into a locale. 305 */ 306 static Locale parseLocale(String localeName) { 307 int score1 = localeName.indexOf('_'); 308 String language, country = "", variant = ""; 309 if (score1 < 0) { 310 language = localeName; 311 } else { 312 language = localeName.substring(0, score1); 313 if (language.length() != 2) { 314 return null; 315 } 316 int score2 = localeName.indexOf('_',score1 + 1); 317 if (score2 < 0) { 318 country = localeName.substring(score1 + 1); 319 if (country.length() != 2) { 320 return null; 321 } 322 } else { 323 country = localeName.substring(score1 + 1, score2); 324 if (country.length() != 2) { 325 return null; 326 } 327 variant = localeName.substring(score2 + 1); 328 } 329 } 330 return new Locale(language,country,variant); 331 } 332 333 /** 334 * Given "happy/BirthdayResource_fr_FR.properties" and ".properties", 335 * returns "happy/BirthdayResource". 336 */ 337 static String fileNameSansLocale(String fileName, String suffix) { 338 String s = removeSuffix(fileName, suffix); 339 // If there are directory names, start reading after the last one. 340 int from = s.lastIndexOf(fileSep); 341 if (from < 0) { 342 from = 0; 343 } 344 while (from < s.length()) { 345 // See whether the rest of the filename after the current 346 // underscore is a valid locale name. If it is, return the 347 // segment of the filename before the current underscore. 348 int score = s.indexOf('_', from); 349 Locale locale = parseLocale(s.substring(score+1)); 350 if (locale != null) { 351 return s.substring(0,score); 352 } 353 from = score + 1; 354 } 355 return s; 356 } 357 358 /** 359 * Converts a chain of {@link Throwable}s into an array. 360 **/ 361 static Throwable[] toArray(Throwable err) 362 { 363 ArrayList list = new ArrayList(); 364 while (err != null) { 365 list.add(err); 366 err = getCause(err); 367 } 368 return (Throwable[]) list.toArray(emptyThrowableArray); 369 } 370 371 private static final Class[] emptyClassArray = new Class[0]; 372 373 private static Throwable getCause(Throwable err) { 374 if (err instanceof InvocationTargetException) { 375 return ((InvocationTargetException) err).getTargetException(); 376 } 377 try { 378 Method method = err.getClass().getMethod( 379 "getCause", emptyClassArray); 380 if (Throwable.class.isAssignableFrom(method.getReturnType())) { 381 return (Throwable) method.invoke(err, new Object[0]); 382 } 383 } catch (NoSuchMethodException e) { 384 } catch (SecurityException e) { 385 } catch (IllegalAccessException e) { 386 } catch (IllegalArgumentException e) { 387 } catch (InvocationTargetException e) { 388 } 389 try { 390 Method method = err.getClass().getMethod( 391 "getNestedThrowable", emptyClassArray); 392 if (Throwable.class.isAssignableFrom(method.getReturnType())) { 393 return (Throwable) method.invoke(err, new Object[0]); 394 } 395 } catch (NoSuchMethodException e) { 396 } catch (SecurityException e) { 397 } catch (IllegalAccessException e) { 398 } catch (IllegalArgumentException e) { 399 } catch (InvocationTargetException e) { 400 } 401 return null; 402 } 403 404 /** 405 * Formats an error, which may have chained errors, as a string. 406 */ 407 static String toString(Throwable err) 408 { 409 StringWriter sw = new StringWriter(); 410 PrintWriter pw = new PrintWriter(sw); 411 Throwable[] throwables = toArray(err); 412 for (int i = 0; i < throwables.length; i++) { 413 Throwable throwable = throwables[i]; 414 if (i > 0) { 415 pw.println(); 416 pw.print("Caused by: "); 417 } 418 pw.print(throwable.toString()); 419 } 420 return sw.toString(); 421 } 422 423 static void printStackTrace(Throwable throwable, PrintWriter s) { 424 Throwable[] stack = Util.toArray(throwable); 425 PrintWriter pw = new DummyPrintWriter(s); 426 for (int i = 0; i < stack.length; i++) { 427 if (i > 0) { 428 pw.println("caused by"); 429 } 430 stack[i].printStackTrace(pw); 431 } 432 pw.flush(); 433 } 434 435 static void printStackTrace(Throwable throwable, PrintStream s) { 436 Throwable[] stack = Util.toArray(throwable); 437 PrintStream ps = new DummyPrintStream(s); 438 for (int i = 0; i < stack.length; i++) { 439 if (i > 0) { 440 ps.println("caused by"); 441 } 442 stack[i].printStackTrace(ps); 443 } 444 ps.flush(); 445 } 446 447 static void generateCommentBlock( 448 PrintWriter pw, 449 String name, 450 String text, 451 String comment) 452 { 453 final String indent = " "; 454 pw.println(indent + "/**"); 455 if (comment != null) { 456 fillText(pw, comment, indent + " * ", "", 70); 457 pw.println(); 458 pw.println(indent + " *"); 459 } 460 pw.print(indent + " * "); 461 fillText( 462 pw, 463 "<code>" + name + "</code> is '<code>" 464 + StringEscaper.xmlEscaper.escapeString(text) + "</code>'", 465 indent + " * ", "", -1); 466 pw.println(); 467 pw.println(indent + " */"); 468 } 469 470 /** 471 * Returns the class name without its package name but with a locale 472 * extension, if applicable. 473 * For example, if class name is <code>happy.BirthdayResource</code>, 474 * and locale is <code>en_US</code>, 475 * returns <code>BirthdayResource_en_US</code>. 476 */ 477 static String getClassNameSansPackage(String className, Locale locale) { 478 String s = className; 479 int lastDot = className.lastIndexOf('.'); 480 if (lastDot >= 0) { 481 s = s.substring(lastDot + 1); 482 } 483 if (locale != null) { 484 s += '_' + locale.toString(); 485 } 486 return s; 487 } 488 489 protected static String removePackage(String s) 490 { 491 int lastDot = s.lastIndexOf('.'); 492 if (lastDot >= 0) { 493 s = s.substring(lastDot + 1); 494 } 495 return s; 496 } 497 498 /** 499 * So we know to avoid recursively calling 500 * {@link Util#printStackTrace(Throwable,java.io.PrintWriter)}. 501 */ 502 static class DummyPrintWriter extends PrintWriter { 503 public DummyPrintWriter(Writer out) { 504 super(out); 505 } 506 } 507 508 /** 509 * So we know to avoid recursively calling 510 * {@link Util#printStackTrace(Throwable,PrintStream)}. 511 */ 512 static class DummyPrintStream extends PrintStream { 513 public DummyPrintStream(OutputStream out) { 514 super(out); 515 } 516 } 517} 518 519// End Util.java