001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: /**
029: * Generates localized string data from source XML file.
030: */package com.sun.midp.l10n.generator;
031:
032: import java.io.*;
033: import java.util.*;
034:
035: import javax.xml.parsers.*;
036: import javax.xml.transform.*;
037: import javax.xml.transform.dom.*;
038: import javax.xml.transform.stream.StreamResult;
039: import javax.xml.transform.stream.StreamSource;
040: import org.w3c.dom.*;
041: import org.xml.sax.ErrorHandler;
042: import org.xml.sax.SAXParseException;
043: import org.xml.sax.SAXException;
044:
045: /**
046: * Driver class with main(). Parses command line arguments and invokes
047: * LocalizedStringsGeneratorImpl instance that does all the transformation work.
048: */
049: public class LocalizedStringsGenerator {
050: /** Transformer that does actual transformation */
051: private static LocalizedStringsGeneratorImpl transformer = null;
052:
053: /** XML file name to apply transformation to */
054: public static String xmlFileName = "";
055:
056: /** Output file name */
057: public static String outFileName = "";
058:
059: /** Print debug output while running */
060: private static boolean debug = false;
061:
062: /** Print usage info and exit */
063: private static boolean printHelp = false;
064:
065: /**
066: * Main method
067: *
068: * @param args Command line arguments
069: */
070: public static void main(String[] args) {
071: try {
072: parseArgs(args);
073: if (printHelp) {
074: printHelp();
075: return;
076: }
077:
078: transformer = new LocalizedStringsGeneratorImpl(debug);
079: transformer.transform(xmlFileName, outFileName);
080: } catch (SAXException e) {
081: // error was already reported
082: e.printStackTrace();
083: System.exit(1);
084: } catch (Exception e) {
085: e.printStackTrace();
086: System.exit(1);
087: }
088: }
089:
090: /**
091: * Parse command line arguments, adding Transformation objects to
092: * <tt>transformations</tt> vector for each transformation specified.
093: *
094: * @param args command line arguments
095: */
096: private static void parseArgs(String[] args) {
097: for (int i = 0; i < args.length; ++i) {
098: String arg = args[i];
099: if (debug) {
100: System.err.println("arg: " + arg);
101: }
102: if (arg.equals("-xml")) {
103: xmlFileName = args[++i];
104: } else if (arg.equals("-out")) {
105: outFileName = args[++i];
106: } else if (arg.equals("-debug")) {
107: debug = true;
108: } else {
109: printHelp = true;
110: }
111: }
112: }
113:
114: /**
115: * Print usage information
116: */
117: private static void printHelp() {
118: /**
119: * Following options are recognized:
120: * -xml: Source XML file.
121: * -out: Output file. If empty, output will be to stdout.
122: * -help: Print usage information
123: * -debug: Be verbose: print some debug info while running.
124: */
125: System.err.println("Usage: java -jar <l10n_generator_jar_file>"
126: + "-xml <localXMLFile> " + "-out <localOutputFile> "
127: + "[-debug] " + "[-help]");
128: }
129: }
130:
131: /**
132: * Perform the transformation
133: */
134: class LocalizedStringsGeneratorImpl {
135: /** Factory constructing Transformer objects */
136: private TransformerFactory transformerFactory = TransformerFactory
137: .newInstance();
138:
139: /** Be verbose: print some debug info while running */
140: private boolean debug = false;
141:
142: /**
143: * Constructor
144: *
145: * @param dbg print some debug output while running
146: */
147: public LocalizedStringsGeneratorImpl(boolean dbg) {
148: debug = dbg;
149: }
150:
151: /**
152: * Converts errors.
153: */
154: class GeneratorErrorHandler implements ErrorHandler {
155:
156: /**
157: * Handles errors.
158: * @param e the parsing exception
159: */
160: public void error(SAXParseException e) throws SAXParseException {
161: reportError(e);
162: // rethrow exception to stop processing on first error
163: throw e;
164: }
165:
166: /**
167: * Handles fatal errors.
168: * @param e the parsing exception
169: */
170: public void fatalError(SAXParseException e) {
171: reportError(e);
172: }
173:
174: /**
175: * Handles warnings.
176: * @param e the parsing exception
177: */
178: public void warning(SAXParseException e) {
179: reportError(e);
180: }
181:
182: /**
183: * Outputs diagnostic messages.
184: * @param e the parsing exception
185: */
186: private void reportError(SAXParseException e) {
187: String msg = e.getMessage();
188: String location = e.getSystemId();
189: int line = e.getLineNumber();
190:
191: System.err.print("Error: URI=" + location);
192: System.err.println(" Line=" + line + ": " + msg);
193: }
194: }
195:
196: /**
197: * Do the actual transformation
198: *
199: * @param tr transformation to perform
200: */
201: public void transform(String xmlFileName, String outFileName)
202: throws Exception {
203:
204: if (debug) {
205: System.err.println("xml file: " + xmlFileName);
206: System.err.println("out file: " + outFileName);
207: }
208:
209: // load XML file as DOM tree
210: DocumentBuilderFactory domFactory = DocumentBuilderFactory
211: .newInstance();
212:
213: DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
214:
215: domBuilder.setErrorHandler(new GeneratorErrorHandler());
216: Document doc = domBuilder.parse(new File(xmlFileName));
217:
218: // make source from it
219: DOMSource source = new DOMSource(doc);
220:
221: generateLocalizedStrings(doc, outFileName);
222: }
223:
224: /**
225: * Creates a directory structure.
226: *
227: * @param fullFileName Full path to the file to be created. If directory
228: * in which file is to be created doesn't exists, it will be created
229: * @exception IOException is thrown if directory couldn't be created
230: */
231: private void makeDirectoryTree(String fullFileName)
232: throws IOException {
233: if (debug == true) {
234: System.out.println("mkdir: " + fullFileName);
235: }
236: int index = fullFileName.lastIndexOf(File.separatorChar);
237: if (index == -1) {
238: // To be compatible with MKS-hosted build on win32, which
239: // does not translate / to \.
240: index = fullFileName.lastIndexOf('/');
241: }
242: File outputDirectory = new File(fullFileName
243: .substring(0, index));
244:
245: if (!(outputDirectory).exists()) {
246: if (!(outputDirectory).mkdirs()) {
247: throw new IOException(
248: "failed to create output directory");
249: }
250: }
251: }
252:
253: /**
254: * Generates localized strings.
255: * @param doc the current working file
256: * @param outDir the target output directory
257: */
258: private void generateLocalizedStrings(Document doc, String outDir)
259: throws Exception {
260: processLocalizedStrings(doc.getDocumentElement(), null);
261: for (Enumeration e = locales.keys(); e.hasMoreElements();) {
262: String key = (String) e.nextElement();
263: LocalizedStringSet locale = (LocalizedStringSet) locales
264: .get(key);
265: String filename = outDir + File.separatorChar
266: + locale.className;
267: makeDirectoryTree(filename);
268:
269: // Write LocalizedStrings<locale>.c file
270: CSourceWriter cwriter = new CSourceWriter();
271: cwriter.writeCSource(locale, filename + ".c",
272: locale.className);
273:
274: // Write LocalizedStrings<locale>.java file
275: FileOutputStream out = new FileOutputStream(filename
276: + ".java");
277: PrintWriter writer = new PrintWriter(
278: new OutputStreamWriter(out));
279: writer
280: .println("// This file is auto-generated. Don't edit!");
281: writer.println("package com.sun.midp.l10n;");
282: writer.println("abstract class " + locale.className + " {");
283: writer
284: .println(" native static String getContent(int index);");
285: writer.println("}");
286: writer.close();
287: }
288: }
289:
290: /**
291: * Walk the XML tree to discover all <localized_string> elements,
292: * collect them and write the data tables specific for each
293: * locale to a pair of Java and C source files.
294: * @param n the current node to be processed
295: * @param className the name of the class to handle this node
296: */
297: private void processLocalizedStrings(Node n, String className)
298: throws Exception {
299: if (n.getNodeName().equals("localized_strings")
300: && (n instanceof Element)) {
301: Element e = (Element) n;
302: String pkg = e.getAttribute("Package");
303: String name = e.getAttribute("Name");
304: className = name;
305: } else if (n.getNodeName().equals("localized_string")
306: && (n instanceof Element) && className != null) {
307: Element e = (Element) n;
308: String value = e.getAttribute("Value");
309: // we use key value as index
310: int valueIndex = Integer.parseInt(e
311: .getAttribute("KeyValue"));
312:
313: addLocalizedString(className, valueIndex, value);
314: }
315:
316: NodeList list = n.getChildNodes();
317: for (int i = 0; i < list.getLength(); i++) {
318: processLocalizedStrings(list.item(i), className);
319: }
320: }
321:
322: /** Supported locales table. */
323: Hashtable locales = new Hashtable();
324:
325: /**
326: * Add one <localized_string> element to the LocalizedStringSet of
327: * the enclosing <localized_strings>.
328: * @param className the locales handler
329: * @param valueIndex key for this entry
330: * @param value data for this entry
331: */
332: private void addLocalizedString(String className, int valueIndex,
333: String value) throws Exception {
334: LocalizedStringSet locale = (LocalizedStringSet) locales
335: .get(className);
336: if (locale == null) {
337: locale = new LocalizedStringSet(className);
338: locales.put(className, locale);
339: }
340: locale.put(valueIndex, value);
341: }
342: }
343:
344: /**
345: * Records all localized strings for the locale represented by
346: * a given Java class.
347: */
348: class LocalizedStringSet {
349: /** This Java class represents a given locale. */
350: String className;
351:
352: /** All localized strings for this locale */
353: Vector strings;
354:
355: /**
356: * Default constructor.
357: * @param className class name of locale handler
358: */
359: LocalizedStringSet(String className) {
360: this .className = className;
361: strings = new Vector(512);
362: }
363:
364: /**
365: * Stores the current entry.
366: * @param idx the key
367: * @param value the data
368: */
369: void put(int idx, String value) {
370: if (strings.size() < idx + 1) {
371: strings.setSize(idx + 1);
372: }
373: strings.set(idx, value);
374: }
375: }
376:
377: /**
378: * This class records related information about each localized string
379: */
380: class LocalizedString {
381: /**
382: * Constructor with initial data.
383: * @param i the key
384: * @param s the value
385: */
386: LocalizedString(int i, String s) {
387: this .index = i;
388: this .string = s;
389: }
390:
391: /** The current search location. */
392: int index;
393: /** The offset of the current item. */
394: int offset;
395: /** The length of the table. */
396: int length;
397: /** The data contents. */
398: String string;
399: }
400:
401: /**
402: * This class deals with generating the LocalizedStringsBase<locale>.c
403: * files. We try to compress the footprint of the data, since we
404: * don't read from the localized string tables very often
405: */
406: class CSourceWriter {
407: /** The locale used. */
408: LocalizedStringSet locale;
409: /** Output stream writer. */
410: PrintWriter writer;
411: /** Array of strings to be processed. */
412: LocalizedString strings[];
413:
414: /**
415: * Sort all string data so they can be more easily compressed
416: * @return the sorted array
417: */
418: ArrayList sort() {
419: ArrayList list = new ArrayList();
420: Vector strTable = locale.strings;
421: for (int i = 0; i < strTable.size(); ++i) {
422: String value = (String) strTable.get(i);
423:
424: if (strings[i] != null) {
425: System.out
426: .println("Duplicated content definiton for index "
427: + i);
428: throw new Error();
429: }
430: strings[i] = new LocalizedString(i, value);
431: list.add(strings[i]);
432: }
433:
434: /**
435: * Sorting function.
436: */
437: Collections.sort(list, new Comparator() {
438: /**
439: * Comparison method.
440: * @param o1 left hand operand
441: * @param o2 right hand operand
442: * @return positive value if o2 is greater than o1,
443: * negative is o2 is less than o1
444: */
445: public int compare(Object o1, Object o2) {
446: LocalizedString r1 = (LocalizedString) o1;
447: LocalizedString r2 = (LocalizedString) o2;
448: return (r1.string.length() < r2.string.length()) ? 1
449: : -1;
450: }
451: });
452:
453: return list;
454: }
455:
456: /**
457: * Writes the C source file that stores the local string data in a
458: * compact data structure
459: * @param locale the target locale for comparison purposes
460: * @param filename the source for processing
461: * @param classname the locale handler
462: */
463: void writeCSource(LocalizedStringSet locale, String filename,
464: String classname) throws IOException {
465: this .locale = locale;
466: this .strings = new LocalizedString[locale.strings.size()];
467:
468: FileOutputStream out = new FileOutputStream(filename);
469: writer = new PrintWriter(new OutputStreamWriter(out));
470: pl("/* This file is auto-generated. Do not edit! */");
471: pl("#include <kni.h>");
472: pl("#include <string.h>");
473: pl("#include <midpMalloc.h>");
474:
475: try {
476: int type = writeOptimizedArray();
477:
478: pl("KNIEXPORT KNI_RETURNTYPE_OBJECT");
479: pl("KNIDECL(com_sun_midp_l10n_" + classname
480: + "_getContent) {");
481: if (type != UNICODE) {
482: pl(" char stackbuffer[128];");
483: pl(" char *utf8; /*0-terminated*/");
484: }
485: pl(" int index = KNI_GetParameterAsInt(1);");
486: pl(" KNI_StartHandles(1);");
487: pl(" KNI_DeclareHandle(string);");
488: pl(" if (index >= 0 && index <= max_index) {");
489: pl(" int offset = (int)offset_data[index * 2 + 0];");
490: pl(" int length = (int)offset_data[index * 2 + 1];");
491: pl(" if (offset >= 0) {");
492: if (type == UNICODE) {
493: plx("KNI_NewString(string_data+offset, length, string);");
494: } else {
495: plx("const char *p = string_data+offset;");
496: plx("if (length < 128) {");
497: plx(" utf8 = stackbuffer;");
498: plx("} else {");
499: plx(" utf8 = (char*)midpMalloc(length+1);");
500: plx("}");
501: plx("if (utf8) {");
502: plx(" memcpy(utf8, p, length);");
503: plx(" utf8[length] = 0;");
504: plx(" KNI_NewStringUTF(utf8, string);");
505: plx("}");
506: plx("if (utf8 && utf8 != stackbuffer) {");
507: plx(" midpFree(utf8);");
508: plx("}");
509: }
510: pl(" }");
511: pl(" }");
512: pl(" KNI_EndHandlesAndReturnObject(string)");
513: pl("}");
514: } catch (Throwable t) {
515: t.printStackTrace();
516: }
517:
518: writer.close();
519: }
520:
521: /**
522: * Short-hand for printing a line to the output file
523: * @param s the string to output
524: */
525: void pl(String s) {
526: writer.println(s);
527: }
528:
529: /**
530: * Short-hand for printing a line to the output file (with a 12-space
531: * prefix)
532: * @param s the string to output
533: */
534: void plx(String s) {
535: writer.print(" ");
536: writer.println(s);
537: }
538:
539: /**
540: * Short-hand for printing a string into the output file
541: * @param s the string to output
542: */
543: void p(String s) {
544: writer.print(s);
545: }
546:
547: /** Indicate that a table should be stored as an UTF8 array in C code. */
548: public final int UTF8 = 1;
549:
550: /** Indicate that a table should be stored as an UNICODE array in C code.*/
551: public final int UNICODE = 2;
552:
553: /**
554: * Write the string data using UTF8 or UNICODE, depending on which
555: * is smaller.
556: * @return the storage type
557: */
558: int writeOptimizedArray() throws Throwable {
559: // (1) Sort all strings and then merge them into a single String.
560: // This allows shorter strings that are substrings of longer strings
561: // to be omitted.
562: ArrayList list = sort();
563: StringBuffer sbuf = new StringBuffer();
564: for (int i = 0; i < list.size(); i++) {
565: LocalizedString r = (LocalizedString) list.get(i);
566: if (r != null) {
567: int n = sbuf.indexOf(r.string);
568: if (n >= 0) {
569: r.offset = n;
570: } else {
571: r.offset = sbuf.length();
572: sbuf.append(r.string);
573: }
574: r.length = r.string.length();
575: }
576: }
577: int type = getStorageType(sbuf);
578: switch (type) {
579: case UTF8:
580: writeUTF8(sbuf, list);
581: break;
582: case UNICODE:
583: writeUNICODE(sbuf);
584: break;
585: }
586:
587: writeOffsetTable();
588:
589: return type;
590: }
591:
592: /**
593: * Tell which one of UTF8 or UNICODE is smaller
594: * @param sbuf the string data to be processed
595: * @return the storage type
596: */
597: int getStorageType(StringBuffer sbuf) throws Throwable {
598: boolean hasZero = false;
599: for (int i = 0; i < sbuf.length(); i++) {
600: char c = sbuf.charAt(i);
601: if (c == 0x0) {
602: hasZero = true;
603: }
604: }
605:
606: if (hasZero) {
607: // The VM's handling of 0 is different than specified in
608: // the "official" UTF8 encoding, so let's not do it.
609: } else {
610: String s = sbuf.toString();
611: byte bytes[] = s.getBytes("UTF-8");
612: if (bytes.length < sbuf.length() * 2) {
613: return UTF8;
614: }
615: }
616:
617: return UNICODE;
618: }
619:
620: /** Hex digits for output. */
621: static char digits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
622: '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
623:
624: /**
625: * Prints a hex number for the given character.
626: * @param c the character to convert
627: * @param numdigits the output field width
628: */
629: void printHex(char c, int numdigits) {
630: p("0x");
631: for (int i = numdigits - 1; i >= 0; i--) {
632: int shift = i * 4;
633: int n = ((((int) c) >> shift)) & 0x0f;
634: writer.print(Character.toString(digits[n]));
635: }
636: }
637:
638: /**
639: * Write all the data in a UNICODE array
640: * @param sbuf the string data to be processed
641: */
642: void writeUNICODE(StringBuffer sbuf) {
643: int STEP = 10;
644: int MAX = sbuf.length();
645: pl("static const jchar string_data[] = {");
646:
647: for (int i = 0; i < MAX; i++) {
648: if ((i % STEP) == 0) {
649: pl("");
650: p(" ");
651: }
652: char c = sbuf.charAt(i);
653: printHex(c, 4);
654: p(",");
655: }
656: pl("};");
657: }
658:
659: /**
660: * Write all the data in a UTF8 array
661: * @param sbuf the string data to be processed
662: * @param list the data to be processed
663: */
664: void writeUTF8(StringBuffer sbuf, ArrayList list) throws Throwable {
665: // Recompute the offset and length of each item
666: byte array[] = new byte[sbuf.length() * 2]; // MAX possible size
667: int end = 0;
668:
669: for (int i = 0; i < list.size(); i++) {
670: LocalizedString r = (LocalizedString) list.get(i);
671: if (r != null) {
672: byte bytes[] = r.string.getBytes("UTF-8");
673: int n = search(bytes, array, end);
674: if (n >= 0) {
675: r.offset = n;
676: } else {
677: r.offset = end;
678: for (int x = 0; x < bytes.length; x++) {
679: array[end++] = bytes[x];
680: }
681: }
682: r.length = bytes.length;
683: }
684: }
685:
686: // Now write the strings to C source code.
687: int STEP = 10;
688: int MAX = end;
689:
690: p("static const char string_data[] = {");
691: for (int i = 0; i < MAX; i++) {
692: if ((i % STEP) == 0) {
693: pl("");
694: p(" ");
695: }
696: char c = (char) array[i];
697: printHex(c, 2);
698: p(",");
699: }
700: pl("};");
701: }
702:
703: /**
704: * Equivalent of String.indexOf(), but works on a UTF8 byte array.
705: * @param needle the item to be found
706: * @param haystack the data to be searched
707: * @param end the limit of the search
708: * @return index of the found data or -1 if not found
709: */
710: int search(byte needle[], byte haystack[], int end) {
711: int needleLen = needle.length;
712: if (needleLen == 0) {
713: return -1;
714: }
715: end -= needleLen;
716: byte b = needle[0];
717:
718: for (int i = 0; i < end; i++) {
719: inner: {
720: if (haystack[i] == b) {
721: for (int j = 1; j < needleLen; j++) {
722: if (haystack[i + j] != needle[j]) {
723: break inner;
724: }
725: }
726: return i;
727: }
728: }
729: }
730:
731: return -1;
732: }
733:
734: /**
735: * Write a table of localized string offsets
736: */
737: void writeOffsetTable() {
738: int maxOffset = 0, maxLen = 0;
739:
740: for (int i = 0; i < strings.length; i++) {
741: LocalizedString r = strings[i];
742: if (r == null) {
743: System.err.println("Warning: resource index " + i
744: + " not defined in class " + locale.className);
745: } else {
746: if (maxOffset < r.offset) {
747: maxOffset = r.offset;
748: }
749: if (maxLen < r.length) {
750: maxLen = r.length;
751: }
752: }
753: }
754: String type = "int";
755: if (maxOffset < 0x7fff && maxLen < 0x7fff) {
756: type = "short";
757: }
758: p("static const " + type + " offset_data[] = {");
759:
760: int STEP = 4;
761: for (int i = 0; i < strings.length; i++) {
762: if ((i % STEP) == 0) {
763: pl("");
764: p(" ");
765: }
766: LocalizedString r = strings[i];
767: if (r == null) {
768: p("-1, -1,");
769: } else {
770: p(r.offset + ", " + r.length + ", ");
771: }
772: }
773: pl("};");
774:
775: pl("static int max_index = " + strings.length + ";");
776: }
777: }
|