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