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