1 /* Writing Java ResourceBundles.
2 Copyright (C) 2001-2003, 2005-2010, 2015 Free Software Foundation,
4 Written by Bruno Haible <haible@clisp.cons.org>, 2001.
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>. */
25 #include "write-java.h"
35 #if !defined S_ISDIR && defined S_IFDIR
36 # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
38 #if !S_IRUSR && S_IREAD
39 # define S_IRUSR S_IREAD
42 # define S_IRUSR 00400
44 #if !S_IWUSR && S_IWRITE
45 # define S_IWUSR S_IWRITE
48 # define S_IWUSR 00200
50 #if !S_IXUSR && S_IEXEC
51 # define S_IXUSR S_IEXEC
54 # define S_IXUSR 00100
60 #include "xvasprintf.h"
64 #include "msgl-iconv.h"
65 #include "plural-exp.h"
66 #include "po-charset.h"
70 #include "concat-filename.h"
71 #include "fwriteerror.h"
72 #include "clean-temp.h"
76 #define _(str) gettext (str)
79 /* Check that the resource name is a valid Java class name. To simplify
80 things, we allow only ASCII characters in the class name.
81 Return the number of dots in the class name, or -1 if not OK. */
83 check_resource_name (const char *name)
90 /* First character, see Character.isJavaIdentifierStart. */
91 if (!(c_isalpha (*p) || (*p == '$') || (*p == '_')))
93 /* Following characters, see Character.isJavaIdentifierPart. */
96 while (c_isalpha (*p) || (*p == '$') || (*p == '_') || c_isdigit (*p));
108 /* Return the Java hash code of a string mod 2^31.
109 The Java String.hashCode() function returns the same values across
110 Java implementations.
111 (See http://www.javasoft.com/docs/books/jls/clarify.html)
112 It returns a signed 32-bit integer. We add a mod 2^31 afterwards;
113 this removes one bit but greatly simplifies the following "mod hash_size"
114 and "mod (hash_size - 2)" operations. */
116 string_hashcode (const char *str)
118 const char *str_limit = str + strlen (str);
120 while (str < str_limit)
123 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
125 /* Single UCS-2 'char'. */
126 hash = 31 * hash + uc;
129 /* UTF-16 surrogate: two 'char's. */
130 ucs4_t uc1 = 0xd800 + ((uc - 0x10000) >> 10);
131 ucs4_t uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff);
132 hash = 31 * hash + uc1;
133 hash = 31 * hash + uc2;
136 return hash & 0x7fffffff;
140 /* Return the Java hash code of a (msgctxt, msgid) pair mod 2^31. */
142 msgid_hashcode (const char *msgctxt, const char *msgid)
145 return string_hashcode (msgid);
148 size_t msgctxt_len = strlen (msgctxt);
149 size_t msgid_len = strlen (msgid);
150 size_t combined_len = msgctxt_len + 1 + msgid_len;
154 combined = (char *) xmalloca (combined_len);
155 memcpy (combined, msgctxt, msgctxt_len);
156 combined[msgctxt_len] = MSGCTXT_SEPARATOR;
157 memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1);
159 result = string_hashcode (combined);
168 /* Compute a good hash table size for the given set of msgids. */
170 compute_hashsize (message_list_ty *mlp, bool *collisionp)
172 /* This is an O(n^2) algorithm, but should be sufficient because few
173 programs have more than 1000 messages in a single domain. */
174 #define XXN 3 /* can be tweaked */
175 #define XXS 3 /* can be tweaked */
176 unsigned int n = mlp->nitems;
177 unsigned int *hashcodes =
178 (unsigned int *) xmalloca (n * sizeof (unsigned int));
179 unsigned int hashsize;
180 unsigned int best_hashsize;
181 unsigned int best_score;
184 for (j = 0; j < n; j++)
185 hashcodes[j] = msgid_hashcode (mlp->item[j]->msgctxt, mlp->item[j]->msgid);
187 /* Try all numbers between n and 3*n. The score depends on the size of the
188 table -- the smaller the better -- and the number of collision lookups,
189 i.e. total number of times that 1 + (hashcode % (hashsize - 2))
190 is added to the index during lookup. If there are collisions, only odd
191 hashsize values are allowed. */
193 best_score = UINT_MAX;
194 for (hashsize = n; hashsize <= XXN * n; hashsize++)
199 /* Premature end of the loop if all future scores are known to be
200 larger than the already reached best_score. This relies on the
201 ascending loop and on the fact that score >= hashsize. */
202 if (hashsize >= best_score)
205 bitmap = XNMALLOC (hashsize, char);
206 memset (bitmap, 0, hashsize);
209 for (j = 0; j < n; j++)
211 unsigned int idx = hashcodes[j] % hashsize;
213 if (bitmap[idx] != 0)
215 /* Collision. Cannot deal with it if hashsize is even. */
216 if ((hashsize % 2) == 0)
217 /* Try next hashsize. */
221 unsigned int idx0 = idx;
222 unsigned int incr = 1 + (hashcodes[j] % (hashsize - 2));
223 score += 2; /* Big penalty for the additional division */
226 score++; /* Small penalty for each loop round */
231 /* Searching for a hole, we performed a whole round
232 across the table. This happens particularly
233 frequently if gcd(hashsize,incr) > 1. Try next
237 while (bitmap[idx] != 0);
243 /* Big hashsize also gives a penalty. */
244 score = XXS * score + hashsize;
246 /* If for any incr between 1 and hashsize - 2, an whole round
247 (idx0, idx0 + incr, ...) is occupied, and the lookup function
248 must deal with collisions, then some inputs would lead to
249 an endless loop in the lookup function. */
250 if (score > hashsize)
254 /* Since the set { idx0, idx0 + incr, ... } depends only on idx0
255 and gcd(hashsize,incr), we only need to conside incr that
257 for (incr = 1; incr <= hashsize / 2; incr++)
258 if ((hashsize % incr) == 0)
262 for (idx0 = 0; idx0 < incr; idx0++)
267 for (idx = idx0; idx < hashsize; idx += incr)
268 if (bitmap[idx] == 0)
274 /* A whole round is occupied. */
286 if (score < best_score)
289 best_hashsize = hashsize;
292 if (best_hashsize == 0 || best_score < best_hashsize)
297 /* There are collisions if and only if best_score > best_hashsize. */
298 *collisionp = (best_score > best_hashsize);
299 return best_hashsize;
303 struct table_item { unsigned int index; message_ty *mp; };
306 compare_index (const void *pval1, const void *pval2)
308 return (int)((const struct table_item *) pval1)->index
309 - (int)((const struct table_item *) pval2)->index;
312 /* Compute the list of messages and table indices, sorted according to the
314 static struct table_item *
315 compute_table_items (message_list_ty *mlp, unsigned int hashsize)
317 unsigned int n = mlp->nitems;
318 struct table_item *arr = XNMALLOC (n, struct table_item);
322 bitmap = XNMALLOC (hashsize, char);
323 memset (bitmap, 0, hashsize);
325 for (j = 0; j < n; j++)
327 unsigned int hashcode =
328 msgid_hashcode (mlp->item[j]->msgctxt, mlp->item[j]->msgid);
329 unsigned int idx = hashcode % hashsize;
331 if (bitmap[idx] != 0)
333 unsigned int incr = 1 + (hashcode % (hashsize - 2));
340 while (bitmap[idx] != 0);
345 arr[j].mp = mlp->item[j];
350 qsort (arr, n, sizeof (arr[0]), compare_index);
356 /* Write a string in Java Unicode notation to the given stream. */
358 write_java_string (FILE *stream, const char *str)
360 static const char hexdigit[] = "0123456789abcdef";
361 const char *str_limit = str + strlen (str);
363 fprintf (stream, "\"");
364 while (str < str_limit)
367 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
370 /* Single UCS-2 'char'. */
372 fprintf (stream, "\\n");
373 else if (uc == 0x000d)
374 fprintf (stream, "\\r");
375 else if (uc == 0x0022)
376 fprintf (stream, "\\\"");
377 else if (uc == 0x005c)
378 fprintf (stream, "\\\\");
379 else if (uc >= 0x0020 && uc < 0x007f)
380 fprintf (stream, "%c", (int) uc);
382 fprintf (stream, "\\u%c%c%c%c",
383 hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
384 hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
388 /* UTF-16 surrogate: two 'char's. */
389 ucs4_t uc1 = 0xd800 + ((uc - 0x10000) >> 10);
390 ucs4_t uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff);
391 fprintf (stream, "\\u%c%c%c%c",
392 hexdigit[(uc1 >> 12) & 0x0f], hexdigit[(uc1 >> 8) & 0x0f],
393 hexdigit[(uc1 >> 4) & 0x0f], hexdigit[uc1 & 0x0f]);
394 fprintf (stream, "\\u%c%c%c%c",
395 hexdigit[(uc2 >> 12) & 0x0f], hexdigit[(uc2 >> 8) & 0x0f],
396 hexdigit[(uc2 >> 4) & 0x0f], hexdigit[uc2 & 0x0f]);
399 fprintf (stream, "\"");
403 /* Write a (msgctxt, msgid) pair as a string in Java Unicode notation to the
406 write_java_msgid (FILE *stream, message_ty *mp)
408 const char *msgctxt = mp->msgctxt;
409 const char *msgid = mp->msgid;
412 write_java_string (stream, msgid);
415 size_t msgctxt_len = strlen (msgctxt);
416 size_t msgid_len = strlen (msgid);
417 size_t combined_len = msgctxt_len + 1 + msgid_len;
420 combined = (char *) xmalloca (combined_len);
421 memcpy (combined, msgctxt, msgctxt_len);
422 combined[msgctxt_len] = MSGCTXT_SEPARATOR;
423 memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1);
425 write_java_string (stream, combined);
432 /* Write Java code that returns the value for a message. If the message
433 has plural forms, it is an expression of type String[], otherwise it is
434 an expression of type String. */
436 write_java_msgstr (FILE *stream, message_ty *mp)
438 if (mp->msgid_plural != NULL)
443 fprintf (stream, "new java.lang.String[] { ");
444 for (p = mp->msgstr, first = true;
445 p < mp->msgstr + mp->msgstr_len;
446 p += strlen (p) + 1, first = false)
449 fprintf (stream, ", ");
450 write_java_string (stream, p);
452 fprintf (stream, " }");
456 if (mp->msgstr_len != strlen (mp->msgstr) + 1)
459 write_java_string (stream, mp->msgstr);
464 /* Writes the body of the function which returns the local value for a key
467 write_lookup_code (FILE *stream, unsigned int hashsize, bool collisions)
469 fprintf (stream, " int hash_val = msgid.hashCode() & 0x7fffffff;\n");
470 fprintf (stream, " int idx = (hash_val %% %d) << 1;\n", hashsize);
473 fprintf (stream, " {\n");
474 fprintf (stream, " java.lang.Object found = table[idx];\n");
475 fprintf (stream, " if (found == null)\n");
476 fprintf (stream, " return null;\n");
477 fprintf (stream, " if (msgid.equals(found))\n");
478 fprintf (stream, " return table[idx + 1];\n");
479 fprintf (stream, " }\n");
480 fprintf (stream, " int incr = ((hash_val %% %d) + 1) << 1;\n",
482 fprintf (stream, " for (;;) {\n");
483 fprintf (stream, " idx += incr;\n");
484 fprintf (stream, " if (idx >= %d)\n", 2 * hashsize);
485 fprintf (stream, " idx -= %d;\n", 2 * hashsize);
486 fprintf (stream, " java.lang.Object found = table[idx];\n");
487 fprintf (stream, " if (found == null)\n");
488 fprintf (stream, " return null;\n");
489 fprintf (stream, " if (msgid.equals(found))\n");
490 fprintf (stream, " return table[idx + 1];\n");
491 fprintf (stream, " }\n");
495 fprintf (stream, " java.lang.Object found = table[idx];\n");
496 fprintf (stream, " if (found != null && msgid.equals(found))\n");
497 fprintf (stream, " return table[idx + 1];\n");
498 fprintf (stream, " return null;\n");
503 /* Tests whether a plural expression, evaluated according to the C rules,
504 can only produce the values 0 and 1. */
506 is_expression_boolean (struct expression *exp)
508 switch (exp->operation)
521 case greater_or_equal:
528 return (exp->val.num == 0 || exp->val.num == 1);
530 return is_expression_boolean (exp->val.args[1])
531 && is_expression_boolean (exp->val.args[2]);
538 /* Write Java code that evaluates a plural expression according to the C rules.
539 The variable is called 'n'. */
541 write_java_expression (FILE *stream, const struct expression *exp, bool as_boolean)
543 /* We use parentheses everywhere. This frees us from tracking the priority
544 of arithmetic operators. */
547 /* Emit a Java expression of type 'boolean'. */
548 switch (exp->operation)
551 fprintf (stream, "%s", exp->val.num ? "true" : "false");
554 fprintf (stream, "(!");
555 write_java_expression (stream, exp->val.args[0], true);
556 fprintf (stream, ")");
559 fprintf (stream, "(");
560 write_java_expression (stream, exp->val.args[0], false);
561 fprintf (stream, " < ");
562 write_java_expression (stream, exp->val.args[1], false);
563 fprintf (stream, ")");
566 fprintf (stream, "(");
567 write_java_expression (stream, exp->val.args[0], false);
568 fprintf (stream, " > ");
569 write_java_expression (stream, exp->val.args[1], false);
570 fprintf (stream, ")");
573 fprintf (stream, "(");
574 write_java_expression (stream, exp->val.args[0], false);
575 fprintf (stream, " <= ");
576 write_java_expression (stream, exp->val.args[1], false);
577 fprintf (stream, ")");
579 case greater_or_equal:
580 fprintf (stream, "(");
581 write_java_expression (stream, exp->val.args[0], false);
582 fprintf (stream, " >= ");
583 write_java_expression (stream, exp->val.args[1], false);
584 fprintf (stream, ")");
587 fprintf (stream, "(");
588 write_java_expression (stream, exp->val.args[0], false);
589 fprintf (stream, " == ");
590 write_java_expression (stream, exp->val.args[1], false);
591 fprintf (stream, ")");
594 fprintf (stream, "(");
595 write_java_expression (stream, exp->val.args[0], false);
596 fprintf (stream, " != ");
597 write_java_expression (stream, exp->val.args[1], false);
598 fprintf (stream, ")");
601 fprintf (stream, "(");
602 write_java_expression (stream, exp->val.args[0], true);
603 fprintf (stream, " && ");
604 write_java_expression (stream, exp->val.args[1], true);
605 fprintf (stream, ")");
608 fprintf (stream, "(");
609 write_java_expression (stream, exp->val.args[0], true);
610 fprintf (stream, " || ");
611 write_java_expression (stream, exp->val.args[1], true);
612 fprintf (stream, ")");
615 if (is_expression_boolean (exp->val.args[1])
616 && is_expression_boolean (exp->val.args[2]))
618 fprintf (stream, "(");
619 write_java_expression (stream, exp->val.args[0], true);
620 fprintf (stream, " ? ");
621 write_java_expression (stream, exp->val.args[1], true);
622 fprintf (stream, " : ");
623 write_java_expression (stream, exp->val.args[2], true);
624 fprintf (stream, ")");
634 fprintf (stream, "(");
635 write_java_expression (stream, exp, false);
636 fprintf (stream, " != 0)");
644 /* Emit a Java expression of type 'long'. */
645 switch (exp->operation)
648 fprintf (stream, "n");
651 fprintf (stream, "%lu", exp->val.num);
654 fprintf (stream, "(");
655 write_java_expression (stream, exp->val.args[0], false);
656 fprintf (stream, " * ");
657 write_java_expression (stream, exp->val.args[1], false);
658 fprintf (stream, ")");
661 fprintf (stream, "(");
662 write_java_expression (stream, exp->val.args[0], false);
663 fprintf (stream, " / ");
664 write_java_expression (stream, exp->val.args[1], false);
665 fprintf (stream, ")");
668 fprintf (stream, "(");
669 write_java_expression (stream, exp->val.args[0], false);
670 fprintf (stream, " %% ");
671 write_java_expression (stream, exp->val.args[1], false);
672 fprintf (stream, ")");
675 fprintf (stream, "(");
676 write_java_expression (stream, exp->val.args[0], false);
677 fprintf (stream, " + ");
678 write_java_expression (stream, exp->val.args[1], false);
679 fprintf (stream, ")");
682 fprintf (stream, "(");
683 write_java_expression (stream, exp->val.args[0], false);
684 fprintf (stream, " - ");
685 write_java_expression (stream, exp->val.args[1], false);
686 fprintf (stream, ")");
689 fprintf (stream, "(");
690 write_java_expression (stream, exp->val.args[0], true);
691 fprintf (stream, " ? ");
692 write_java_expression (stream, exp->val.args[1], false);
693 fprintf (stream, " : ");
694 write_java_expression (stream, exp->val.args[2], false);
695 fprintf (stream, ")");
701 case greater_or_equal:
706 fprintf (stream, "(");
707 write_java_expression (stream, exp, true);
708 fprintf (stream, " ? 1 : 0)");
717 /* Write the Java initialization statements for the Java 1.1.x case,
718 for items j, start_index <= j < end_index. */
720 write_java1_init_statements (FILE *stream, message_list_ty *mlp,
721 size_t start_index, size_t end_index)
725 for (j = start_index; j < end_index; j++)
727 fprintf (stream, " t.put(");
728 write_java_msgid (stream, mlp->item[j]);
729 fprintf (stream, ",");
730 write_java_msgstr (stream, mlp->item[j]);
731 fprintf (stream, ");\n");
736 /* Write the Java initialization statements for the Java 2 case,
737 for items j, start_index <= j < end_index. */
739 write_java2_init_statements (FILE *stream, message_list_ty *mlp,
740 const struct table_item *table_items,
741 size_t start_index, size_t end_index)
745 for (j = start_index; j < end_index; j++)
747 const struct table_item *ti = &table_items[j];
749 fprintf (stream, " t[%d] = ", 2 * ti->index);
750 write_java_msgid (stream, ti->mp);
751 fprintf (stream, ";\n");
752 fprintf (stream, " t[%d] = ", 2 * ti->index + 1);
753 write_java_msgstr (stream, ti->mp);
754 fprintf (stream, ";\n");
759 /* Write the Java code for the ResourceBundle subclass to the given stream.
760 Note that we use fully qualified class names and no "import" statements,
761 because applications can have their own classes called X.Y.ResourceBundle
764 write_java_code (FILE *stream, const char *class_name, message_list_ty *mlp,
767 const char *last_dot;
768 unsigned int plurals;
772 "/* Automatically generated by GNU msgfmt. Do not modify! */\n");
773 last_dot = strrchr (class_name, '.');
774 if (last_dot != NULL)
776 fprintf (stream, "package ");
777 fwrite (class_name, 1, last_dot - class_name, stream);
778 fprintf (stream, ";\npublic class %s", last_dot + 1);
781 fprintf (stream, "public class %s", class_name);
782 fprintf (stream, " extends java.util.ResourceBundle {\n");
784 /* Determine whether there are plural messages. */
786 for (j = 0; j < mlp->nitems; j++)
787 if (mlp->item[j]->msgid_plural != NULL)
792 unsigned int hashsize;
794 struct table_item *table_items;
795 const char *table_eltype;
797 /* Determine the hash table size and whether it leads to collisions. */
798 hashsize = compute_hashsize (mlp, &collisions);
800 /* Determines which indices in the table contain a message. The others
802 table_items = compute_table_items (mlp, hashsize);
804 /* Emit the table of pairs (msgid, msgstr). If there are plurals,
805 it is of type Object[], otherwise of type String[]. We use a static
806 code block because that makes less code: The Java compilers also
807 generate code for the 'null' entries, which is dumb. */
808 table_eltype = (plurals ? "java.lang.Object" : "java.lang.String");
809 fprintf (stream, " private static final %s[] table;\n", table_eltype);
811 /* With the Sun javac compiler, each assignment takes 5 to 8 bytes
812 of bytecode, therefore for each message, up to 16 bytes are needed.
813 Since the bytecode of every method, including the <clinit> method
814 that contains the static initializers, is limited to 64 KB, only ca,
815 65536 / 16 = 4096 messages can be initialized in a single method.
816 Account for other Java compilers and for plurals by limiting it to
818 const size_t max_items_per_method = 1000;
820 if (mlp->nitems > max_items_per_method)
826 for (k = 0, start_j = 0, end_j = start_j + max_items_per_method;
827 start_j < mlp->nitems;
828 k++, start_j = end_j, end_j = start_j + max_items_per_method)
830 fprintf (stream, " static void clinit_part_%u (%s[] t) {\n",
832 write_java2_init_statements (stream, mlp, table_items,
833 start_j, MIN (end_j, mlp->nitems));
834 fprintf (stream, " }\n");
837 fprintf (stream, " static {\n");
838 fprintf (stream, " %s[] t = new %s[%d];\n", table_eltype,
839 table_eltype, 2 * hashsize);
840 if (mlp->nitems > max_items_per_method)
845 for (k = 0, start_j = 0;
846 start_j < mlp->nitems;
847 k++, start_j += max_items_per_method)
848 fprintf (stream, " clinit_part_%u(t);\n", k);
851 write_java2_init_statements (stream, mlp, table_items,
853 fprintf (stream, " table = t;\n");
854 fprintf (stream, " }\n");
857 /* Emit the msgid_plural strings. Only used by msgunfmt. */
861 fprintf (stream, " public static final java.lang.String[] get_msgid_plural_table () {\n");
862 fprintf (stream, " return new java.lang.String[] { ");
864 for (j = 0; j < mlp->nitems; j++)
866 struct table_item *ti = &table_items[j];
867 if (ti->mp->msgid_plural != NULL)
870 fprintf (stream, ", ");
871 write_java_string (stream, ti->mp->msgid_plural);
875 fprintf (stream, " };\n");
876 fprintf (stream, " }\n");
881 /* Emit the lookup function. It is a common subroutine for
882 handleGetObject and ngettext. */
883 fprintf (stream, " public java.lang.Object lookup (java.lang.String msgid) {\n");
884 write_lookup_code (stream, hashsize, collisions);
885 fprintf (stream, " }\n");
888 /* Emit the handleGetObject function. It is declared abstract in
889 ResourceBundle. It implements a local version of gettext. */
890 fprintf (stream, " public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {\n");
893 fprintf (stream, " java.lang.Object value = lookup(msgid);\n");
894 fprintf (stream, " return (value instanceof java.lang.String[] ? ((java.lang.String[])value)[0] : value);\n");
897 write_lookup_code (stream, hashsize, collisions);
898 fprintf (stream, " }\n");
900 /* Emit the getKeys function. It is declared abstract in ResourceBundle.
901 The inner class is not avoidable. */
902 fprintf (stream, " public java.util.Enumeration getKeys () {\n");
903 fprintf (stream, " return\n");
904 fprintf (stream, " new java.util.Enumeration() {\n");
905 fprintf (stream, " private int idx = 0;\n");
906 fprintf (stream, " { while (idx < %d && table[idx] == null) idx += 2; }\n",
908 fprintf (stream, " public boolean hasMoreElements () {\n");
909 fprintf (stream, " return (idx < %d);\n", 2 * hashsize);
910 fprintf (stream, " }\n");
911 fprintf (stream, " public java.lang.Object nextElement () {\n");
912 fprintf (stream, " java.lang.Object key = table[idx];\n");
913 fprintf (stream, " do idx += 2; while (idx < %d && table[idx] == null);\n",
915 fprintf (stream, " return key;\n");
916 fprintf (stream, " }\n");
917 fprintf (stream, " };\n");
918 fprintf (stream, " }\n");
922 /* Java 1.1.x uses a different hash function. If compatibility with
923 this Java version is required, the hash table must be built at run time,
924 not at compile time. */
925 fprintf (stream, " private static final java.util.Hashtable table;\n");
927 /* With the Sun javac compiler, each 'put' call takes 9 to 11 bytes
928 of bytecode, therefore for each message, up to 11 bytes are needed.
929 Since the bytecode of every method, including the <clinit> method
930 that contains the static initializers, is limited to 64 KB, only ca,
931 65536 / 11 = 5958 messages can be initialized in a single method.
932 Account for other Java compilers and for plurals by limiting it to
934 const size_t max_items_per_method = 1500;
936 if (mlp->nitems > max_items_per_method)
942 for (k = 0, start_j = 0, end_j = start_j + max_items_per_method;
943 start_j < mlp->nitems;
944 k++, start_j = end_j, end_j = start_j + max_items_per_method)
946 fprintf (stream, " static void clinit_part_%u (java.util.Hashtable t) {\n",
948 write_java1_init_statements (stream, mlp,
949 start_j, MIN (end_j, mlp->nitems));
950 fprintf (stream, " }\n");
953 fprintf (stream, " static {\n");
954 fprintf (stream, " java.util.Hashtable t = new java.util.Hashtable();\n");
955 if (mlp->nitems > max_items_per_method)
960 for (k = 0, start_j = 0;
961 start_j < mlp->nitems;
962 k++, start_j += max_items_per_method)
963 fprintf (stream, " clinit_part_%u(t);\n", k);
966 write_java1_init_statements (stream, mlp, 0, mlp->nitems);
967 fprintf (stream, " table = t;\n");
968 fprintf (stream, " }\n");
971 /* Emit the msgid_plural strings. Only used by msgunfmt. */
974 fprintf (stream, " public static final java.util.Hashtable get_msgid_plural_table () {\n");
975 fprintf (stream, " java.util.Hashtable p = new java.util.Hashtable();\n");
976 for (j = 0; j < mlp->nitems; j++)
977 if (mlp->item[j]->msgid_plural != NULL)
979 fprintf (stream, " p.put(");
980 write_java_msgid (stream, mlp->item[j]);
981 fprintf (stream, ",");
982 write_java_string (stream, mlp->item[j]->msgid_plural);
983 fprintf (stream, ");\n");
985 fprintf (stream, " return p;\n");
986 fprintf (stream, " }\n");
991 /* Emit the lookup function. It is a common subroutine for
992 handleGetObject and ngettext. */
993 fprintf (stream, " public java.lang.Object lookup (java.lang.String msgid) {\n");
994 fprintf (stream, " return table.get(msgid);\n");
995 fprintf (stream, " }\n");
998 /* Emit the handleGetObject function. It is declared abstract in
999 ResourceBundle. It implements a local version of gettext. */
1000 fprintf (stream, " public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {\n");
1003 fprintf (stream, " java.lang.Object value = table.get(msgid);\n");
1004 fprintf (stream, " return (value instanceof java.lang.String[] ? ((java.lang.String[])value)[0] : value);\n");
1007 fprintf (stream, " return table.get(msgid);\n");
1008 fprintf (stream, " }\n");
1010 /* Emit the getKeys function. It is declared abstract in
1012 fprintf (stream, " public java.util.Enumeration getKeys () {\n");
1013 fprintf (stream, " return table.keys();\n");
1014 fprintf (stream, " }\n");
1017 /* Emit the pluralEval function. It is a subroutine for ngettext. */
1020 message_ty *header_entry;
1021 const struct expression *plural;
1022 unsigned long int nplurals;
1024 header_entry = message_list_search (mlp, NULL, "");
1025 extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
1026 &plural, &nplurals);
1028 fprintf (stream, " public static long pluralEval (long n) {\n");
1029 fprintf (stream, " return ");
1030 write_java_expression (stream, plural, false);
1031 fprintf (stream, ";\n");
1032 fprintf (stream, " }\n");
1035 /* Emit the getParent function. It is a subroutine for ngettext. */
1036 fprintf (stream, " public java.util.ResourceBundle getParent () {\n");
1037 fprintf (stream, " return parent;\n");
1038 fprintf (stream, " }\n");
1040 fprintf (stream, "}\n");
1045 msgdomain_write_java (message_list_ty *mlp, const char *canon_encoding,
1046 const char *resource_name, const char *locale_name,
1047 const char *directory,
1052 struct temp_dir *tmpdir;
1056 char *java_file_name;
1058 const char *java_sources[1];
1059 const char *source_dir_name;
1061 /* If no entry for this resource/domain, don't even create the file. */
1062 if (mlp->nitems == 0)
1067 /* Convert the messages to Unicode. */
1068 iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
1073 source_dir_name = directory;
1077 /* Create a temporary directory where we can put the Java file. */
1078 tmpdir = create_temp_dir ("msg", NULL, false);
1081 source_dir_name = tmpdir->dir_name;
1084 /* Assign a default value to the resource name. */
1085 if (resource_name == NULL)
1086 resource_name = "Messages";
1088 /* Prepare the list of subdirectories. */
1089 ndots = check_resource_name (resource_name);
1092 error (0, 0, _("not a valid Java class name: %s"), resource_name);
1096 if (locale_name != NULL)
1097 class_name = xasprintf ("%s_%s", resource_name, locale_name);
1099 class_name = xstrdup (resource_name);
1101 subdirs = (ndots > 0 ? (char **) xmalloca (ndots * sizeof (char *)) : NULL);
1104 const char *last_dir;
1107 last_dir = source_dir_name;
1109 for (i = 0; i < ndots; i++)
1111 const char *q = strchr (p, '.');
1113 char *part = (char *) xmalloca (n + 1);
1114 memcpy (part, p, n);
1116 subdirs[i] = xconcatenated_filename (last_dir, part, NULL);
1118 last_dir = subdirs[i];
1122 if (locale_name != NULL)
1124 char *suffix = xasprintf ("_%s.java", locale_name);
1125 java_file_name = xconcatenated_filename (last_dir, p, suffix);
1129 java_file_name = xconcatenated_filename (last_dir, p, ".java");
1132 /* If OUTPUT_SOURCE, write the Java file in DIRECTORY and return. */
1137 for (i = 0; i < ndots; i++)
1139 if (mkdir (subdirs[i], S_IRUSR | S_IWUSR | S_IXUSR) < 0)
1141 error (0, errno, _("failed to create \"%s\""), subdirs[i]);
1146 java_file = fopen (java_file_name, "w");
1147 if (java_file == NULL)
1149 error (0, errno, _("failed to create \"%s\""), java_file_name);
1153 write_java_code (java_file, class_name, mlp, assume_java2);
1155 if (fwriteerror (java_file))
1157 error (0, errno, _("error while writing \"%s\" file"),
1166 /* Create the subdirectories. This is needed because some older Java
1167 compilers verify that the source of class A.B.C really sits in a
1168 directory whose name ends in /A/B. */
1172 for (i = 0; i < ndots; i++)
1174 register_temp_subdir (tmpdir, subdirs[i]);
1175 if (mkdir (subdirs[i], S_IRUSR | S_IWUSR | S_IXUSR) < 0)
1177 error (0, errno, _("failed to create \"%s\""), subdirs[i]);
1178 unregister_temp_subdir (tmpdir, subdirs[i]);
1184 /* Create the Java file. */
1185 register_temp_file (tmpdir, java_file_name);
1186 java_file = fopen_temp (java_file_name, "w");
1187 if (java_file == NULL)
1189 error (0, errno, _("failed to create \"%s\""), java_file_name);
1190 unregister_temp_file (tmpdir, java_file_name);
1194 write_java_code (java_file, class_name, mlp, assume_java2);
1196 if (fwriteerror_temp (java_file))
1198 error (0, errno, _("error while writing \"%s\" file"), java_file_name);
1202 /* Compile the Java file to a .class file.
1203 directory must be non-NULL, because when the -d option is omitted, the
1204 Java compilers create the class files in the source file's directory -
1205 which is in a temporary directory in our case. */
1206 java_sources[0] = java_file_name;
1207 if (compile_java_class (java_sources, 1, NULL, 0, "1.3", "1.1", directory,
1208 true, false, true, verbose > 0))
1212 compilation of Java class failed, please try --verbose or set $JAVAC"));
1215 compilation of Java class failed, please try to set $JAVAC"));
1224 free (java_file_name);
1225 for (i = 0; i < ndots; i++)
1232 cleanup_temp_dir (tmpdir);