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
024 package org.eigenbase.resgen;
025 import org.eigenbase.xom.*;
026
027 import java.io.*;
028 import java.lang.reflect.InvocationTargetException;
029 import java.lang.reflect.Method;
030 import java.net.MalformedURLException;
031 import java.net.URL;
032 import java.util.ArrayList;
033 import 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 **/
043 abstract 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