1 /* Writing Java ResourceBundles.
2 Copyright (C) 2001-2003, 2005-2006 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 #include <alloca.h>
23
24 /* Specification. */
25 #include "write-java.h"
26
27 #include <errno.h>
28 #include <limits.h>
29 #include <stdbool.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <string.h>
33
34 #include <sys/stat.h>
35 #if !defined S_ISDIR && defined S_IFDIR
36 # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
37 #endif
38 #if !S_IRUSR && S_IREAD
39 # define S_IRUSR S_IREAD
40 #endif
41 #if !S_IRUSR
42 # define S_IRUSR 00400
43 #endif
44 #if !S_IWUSR && S_IWRITE
45 # define S_IWUSR S_IWRITE
46 #endif
47 #if !S_IWUSR
48 # define S_IWUSR 00200
49 #endif
50 #if !S_IXUSR && S_IEXEC
51 # define S_IXUSR S_IEXEC
52 #endif
53 #if !S_IXUSR
54 # define S_IXUSR 00100
55 #endif
56
57 #ifdef __MINGW32__
58 # include <io.h>
59 /* mingw's _mkdir() function has 1 argument, but we pass 2 arguments.
60 Therefore we have to disable the argument count checking. */
61 # define mkdir ((int (*)()) _mkdir)
62 #endif
63
64 #include "c-ctype.h"
65 #include "error.h"
66 #include "xerror.h"
67 #include "xvasprintf.h"
68 #include "javacomp.h"
69 #include "message.h"
70 #include "msgfmt.h"
71 #include "msgl-iconv.h"
72 #include "plural-exp.h"
73 #include "po-charset.h"
74 #include "xalloc.h"
75 #include "xallocsa.h"
76 #include "pathname.h"
77 #include "fwriteerror.h"
78 #include "clean-temp.h"
79 #include "utf8-ucs4.h"
80 #include "gettext.h"
81
82 #define _(str) gettext (str)
83
84
85 /* Check that the resource name is a valid Java class name. To simplify
86 things, we allow only ASCII characters in the class name.
87 Return the number of dots in the class name, or -1 if not OK. */
88 static int
check_resource_name(const char * name)89 check_resource_name (const char *name)
90 {
91 int ndots = 0;
92 const char *p = name;
93
94 for (;;)
95 {
96 /* First character, see Character.isJavaIdentifierStart. */
97 if (!(c_isalpha (*p) || (*p == '$') || (*p == '_')))
98 return -1;
99 /* Following characters, see Character.isJavaIdentifierPart. */
100 do
101 p++;
102 while (c_isalpha (*p) || (*p == '$') || (*p == '_') || c_isdigit (*p));
103 if (*p == '\0')
104 break;
105 if (*p != '.')
106 return -1;
107 p++;
108 ndots++;
109 }
110 return ndots;
111 }
112
113
114 /* Return the Java hash code of a string mod 2^31.
115 The Java String.hashCode() function returns the same values across
116 Java implementations.
117 (See http://www.javasoft.com/docs/books/jls/clarify.html)
118 It returns a signed 32-bit integer. We add a mod 2^31 afterwards;
119 this removes one bit but greatly simplifies the following "mod hash_size"
120 and "mod (hash_size - 2)" operations. */
121 static unsigned int
string_hashcode(const char * str)122 string_hashcode (const char *str)
123 {
124 const char *str_limit = str + strlen (str);
125 int hash = 0;
126 while (str < str_limit)
127 {
128 unsigned int uc;
129 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
130 if (uc < 0x10000)
131 /* Single UCS-2 'char'. */
132 hash = 31 * hash + uc;
133 else
134 {
135 /* UTF-16 surrogate: two 'char's. */
136 unsigned int uc1 = 0xd800 + ((uc - 0x10000) >> 10);
137 unsigned int uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff);
138 hash = 31 * hash + uc1;
139 hash = 31 * hash + uc2;
140 }
141 }
142 return hash & 0x7fffffff;
143 }
144
145
146 /* Compute a good hash table size for the given set of msgids. */
147 static unsigned int
compute_hashsize(message_list_ty * mlp,bool * collisionp)148 compute_hashsize (message_list_ty *mlp, bool *collisionp)
149 {
150 /* This is an O(n^2) algorithm, but should be sufficient because few
151 programs have more than 1000 messages in a single domain. */
152 #define XXN 3 /* can be tweaked */
153 #define XXS 3 /* can be tweaked */
154 unsigned int n = mlp->nitems;
155 unsigned int *hashcodes =
156 (unsigned int *) xallocsa (n * sizeof (unsigned int));
157 unsigned int hashsize;
158 unsigned int best_hashsize;
159 unsigned int best_score;
160 size_t j;
161
162 for (j = 0; j < n; j++)
163 hashcodes[j] = string_hashcode (mlp->item[j]->msgid);
164
165 /* Try all numbers between n and 3*n. The score depends on the size of the
166 table -- the smaller the better -- and the number of collision lookups,
167 i.e. total number of times that 1 + (hashcode % (hashsize - 2))
168 is added to the index during lookup. If there are collisions, only odd
169 hashsize values are allowed. */
170 best_hashsize = 0;
171 best_score = UINT_MAX;
172 for (hashsize = n; hashsize <= XXN * n; hashsize++)
173 {
174 char *bitmap;
175 unsigned int score;
176
177 /* Premature end of the loop if all future scores are known to be
178 larger than the already reached best_score. This relies on the
179 ascending loop and on the fact that score >= hashsize. */
180 if (hashsize >= best_score)
181 break;
182
183 bitmap = (char *) xmalloc (hashsize);
184 memset (bitmap, 0, hashsize);
185
186 score = 0;
187 for (j = 0; j < n; j++)
188 {
189 unsigned int idx = hashcodes[j] % hashsize;
190
191 if (bitmap[idx] != 0)
192 {
193 /* Collision. Cannot deal with it if hashsize is even. */
194 if ((hashsize % 2) == 0)
195 /* Try next hashsize. */
196 goto bad_hashsize;
197 else
198 {
199 unsigned int idx0 = idx;
200 unsigned int incr = 1 + (hashcodes[j] % (hashsize - 2));
201 score += 2; /* Big penalty for the additional division */
202 do
203 {
204 score++; /* Small penalty for each loop round */
205 idx += incr;
206 if (idx >= hashsize)
207 idx -= hashsize;
208 if (idx == idx0)
209 /* Searching for a hole, we performed a whole round
210 across the table. This happens particularly
211 frequently if gcd(hashsize,incr) > 1. Try next
212 hashsize. */
213 goto bad_hashsize;
214 }
215 while (bitmap[idx] != 0);
216 }
217 }
218 bitmap[idx] = 1;
219 }
220
221 /* Big hashsize also gives a penalty. */
222 score = XXS * score + hashsize;
223
224 /* If for any incr between 1 and hashsize - 2, an whole round
225 (idx0, idx0 + incr, ...) is occupied, and the lookup function
226 must deal with collisions, then some inputs would lead to
227 an endless loop in the lookup function. */
228 if (score > hashsize)
229 {
230 unsigned int incr;
231
232 /* Since the set { idx0, idx0 + incr, ... } depends only on idx0
233 and gcd(hashsize,incr), we only need to conside incr that
234 divides hashsize. */
235 for (incr = 1; incr <= hashsize / 2; incr++)
236 if ((hashsize % incr) == 0)
237 {
238 unsigned int idx0;
239
240 for (idx0 = 0; idx0 < incr; idx0++)
241 {
242 bool full = true;
243 unsigned int idx;
244
245 for (idx = idx0; idx < hashsize; idx += incr)
246 if (bitmap[idx] == 0)
247 {
248 full = false;
249 break;
250 }
251 if (full)
252 /* A whole round is occupied. */
253 goto bad_hashsize;
254 }
255 }
256 }
257
258 if (false)
259 bad_hashsize:
260 score = UINT_MAX;
261
262 free (bitmap);
263
264 if (score < best_score)
265 {
266 best_score = score;
267 best_hashsize = hashsize;
268 }
269 }
270 if (best_hashsize == 0 || best_score < best_hashsize)
271 abort ();
272
273 freesa (hashcodes);
274
275 /* There are collisions if and only if best_score > best_hashsize. */
276 *collisionp = (best_score > best_hashsize);
277 return best_hashsize;
278 }
279
280
281 struct table_item { unsigned int index; message_ty *mp; };
282
283 static int
compare_index(const void * pval1,const void * pval2)284 compare_index (const void *pval1, const void *pval2)
285 {
286 return (int)((const struct table_item *) pval1)->index
287 - (int)((const struct table_item *) pval2)->index;
288 }
289
290 /* Compute the list of messages and table indices, sorted according to the
291 indices. */
292 static struct table_item *
compute_table_items(message_list_ty * mlp,unsigned int hashsize)293 compute_table_items (message_list_ty *mlp, unsigned int hashsize)
294 {
295 unsigned int n = mlp->nitems;
296 struct table_item *arr =
297 (struct table_item *) xmalloc (n * sizeof (struct table_item));
298 char *bitmap;
299 size_t j;
300
301 bitmap = (char *) xmalloc (hashsize);
302 memset (bitmap, 0, hashsize);
303
304 for (j = 0; j < n; j++)
305 {
306 unsigned int hashcode = string_hashcode (mlp->item[j]->msgid);
307 unsigned int idx = hashcode % hashsize;
308
309 if (bitmap[idx] != 0)
310 {
311 unsigned int incr = 1 + (hashcode % (hashsize - 2));
312 do
313 {
314 idx += incr;
315 if (idx >= hashsize)
316 idx -= hashsize;
317 }
318 while (bitmap[idx] != 0);
319 }
320 bitmap[idx] = 1;
321
322 arr[j].index = idx;
323 arr[j].mp = mlp->item[j];
324 }
325
326 free (bitmap);
327
328 qsort (arr, n, sizeof (arr[0]), compare_index);
329
330 return arr;
331 }
332
333
334 /* Write a string in Java Unicode notation to the given stream. */
335 static void
write_java_string(FILE * stream,const char * str)336 write_java_string (FILE *stream, const char *str)
337 {
338 static const char hexdigit[] = "0123456789abcdef";
339 const char *str_limit = str + strlen (str);
340
341 fprintf (stream, "\"");
342 while (str < str_limit)
343 {
344 unsigned int uc;
345 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
346 if (uc < 0x10000)
347 {
348 /* Single UCS-2 'char'. */
349 if (uc == 0x000a)
350 fprintf (stream, "\\n");
351 else if (uc == 0x000d)
352 fprintf (stream, "\\r");
353 else if (uc == 0x0022)
354 fprintf (stream, "\\\"");
355 else if (uc == 0x005c)
356 fprintf (stream, "\\\\");
357 else if (uc >= 0x0020 && uc < 0x007f)
358 fprintf (stream, "%c", uc);
359 else
360 fprintf (stream, "\\u%c%c%c%c",
361 hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
362 hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
363 }
364 else
365 {
366 /* UTF-16 surrogate: two 'char's. */
367 unsigned int uc1 = 0xd800 + ((uc - 0x10000) >> 10);
368 unsigned int uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff);
369 fprintf (stream, "\\u%c%c%c%c",
370 hexdigit[(uc1 >> 12) & 0x0f], hexdigit[(uc1 >> 8) & 0x0f],
371 hexdigit[(uc1 >> 4) & 0x0f], hexdigit[uc1 & 0x0f]);
372 fprintf (stream, "\\u%c%c%c%c",
373 hexdigit[(uc2 >> 12) & 0x0f], hexdigit[(uc2 >> 8) & 0x0f],
374 hexdigit[(uc2 >> 4) & 0x0f], hexdigit[uc2 & 0x0f]);
375 }
376 }
377 fprintf (stream, "\"");
378 }
379
380
381 /* Write Java code that returns the value for a message. If the message
382 has plural forms, it is an expression of type String[], otherwise it is
383 an expression of type String. */
384 static void
write_java_msgstr(FILE * stream,message_ty * mp)385 write_java_msgstr (FILE *stream, message_ty *mp)
386 {
387 if (mp->msgid_plural != NULL)
388 {
389 bool first;
390 const char *p;
391
392 fprintf (stream, "new java.lang.String[] { ");
393 for (p = mp->msgstr, first = true;
394 p < mp->msgstr + mp->msgstr_len;
395 p += strlen (p) + 1, first = false)
396 {
397 if (!first)
398 fprintf (stream, ", ");
399 write_java_string (stream, p);
400 }
401 fprintf (stream, " }");
402 }
403 else
404 {
405 if (mp->msgstr_len != strlen (mp->msgstr) + 1)
406 abort ();
407
408 write_java_string (stream, mp->msgstr);
409 }
410 }
411
412
413 /* Writes the body of the function which returns the local value for a key
414 named 'msgid'. */
415 static void
write_lookup_code(FILE * stream,unsigned int hashsize,bool collisions)416 write_lookup_code (FILE *stream, unsigned int hashsize, bool collisions)
417 {
418 fprintf (stream, " int hash_val = msgid.hashCode() & 0x7fffffff;\n");
419 fprintf (stream, " int idx = (hash_val %% %d) << 1;\n", hashsize);
420 if (collisions)
421 {
422 fprintf (stream, " {\n");
423 fprintf (stream, " java.lang.Object found = table[idx];\n");
424 fprintf (stream, " if (found == null)\n");
425 fprintf (stream, " return null;\n");
426 fprintf (stream, " if (msgid.equals(found))\n");
427 fprintf (stream, " return table[idx + 1];\n");
428 fprintf (stream, " }\n");
429 fprintf (stream, " int incr = ((hash_val %% %d) + 1) << 1;\n",
430 hashsize - 2);
431 fprintf (stream, " for (;;) {\n");
432 fprintf (stream, " idx += incr;\n");
433 fprintf (stream, " if (idx >= %d)\n", 2 * hashsize);
434 fprintf (stream, " idx -= %d;\n", 2 * hashsize);
435 fprintf (stream, " java.lang.Object found = table[idx];\n");
436 fprintf (stream, " if (found == null)\n");
437 fprintf (stream, " return null;\n");
438 fprintf (stream, " if (msgid.equals(found))\n");
439 fprintf (stream, " return table[idx + 1];\n");
440 fprintf (stream, " }\n");
441 }
442 else
443 {
444 fprintf (stream, " java.lang.Object found = table[idx];\n");
445 fprintf (stream, " if (found != null && msgid.equals(found))\n");
446 fprintf (stream, " return table[idx + 1];\n");
447 fprintf (stream, " return null;\n");
448 }
449 }
450
451
452 /* Tests whether a plural expression, evaluated according to the C rules,
453 can only produce the values 0 and 1. */
454 static bool
is_expression_boolean(struct expression * exp)455 is_expression_boolean (struct expression *exp)
456 {
457 switch (exp->operation)
458 {
459 case var:
460 case mult:
461 case divide:
462 case module:
463 case plus:
464 case minus:
465 return false;
466 case lnot:
467 case less_than:
468 case greater_than:
469 case less_or_equal:
470 case greater_or_equal:
471 case equal:
472 case not_equal:
473 case land:
474 case lor:
475 return true;
476 case num:
477 return (exp->val.num == 0 || exp->val.num == 1);
478 case qmop:
479 return is_expression_boolean (exp->val.args[1])
480 && is_expression_boolean (exp->val.args[2]);
481 default:
482 abort ();
483 }
484 }
485
486
487 /* Write Java code that evaluates a plural expression according to the C rules.
488 The variable is called 'n'. */
489 static void
write_java_expression(FILE * stream,struct expression * exp,bool as_boolean)490 write_java_expression (FILE *stream, struct expression *exp, bool as_boolean)
491 {
492 /* We use parentheses everywhere. This frees us from tracking the priority
493 of arithmetic operators. */
494 if (as_boolean)
495 {
496 /* Emit a Java expression of type 'boolean'. */
497 switch (exp->operation)
498 {
499 case num:
500 fprintf (stream, "%s", exp->val.num ? "true" : "false");
501 return;
502 case lnot:
503 fprintf (stream, "(!");
504 write_java_expression (stream, exp->val.args[0], true);
505 fprintf (stream, ")");
506 return;
507 case less_than:
508 fprintf (stream, "(");
509 write_java_expression (stream, exp->val.args[0], false);
510 fprintf (stream, " < ");
511 write_java_expression (stream, exp->val.args[1], false);
512 fprintf (stream, ")");
513 return;
514 case greater_than:
515 fprintf (stream, "(");
516 write_java_expression (stream, exp->val.args[0], false);
517 fprintf (stream, " > ");
518 write_java_expression (stream, exp->val.args[1], false);
519 fprintf (stream, ")");
520 return;
521 case less_or_equal:
522 fprintf (stream, "(");
523 write_java_expression (stream, exp->val.args[0], false);
524 fprintf (stream, " <= ");
525 write_java_expression (stream, exp->val.args[1], false);
526 fprintf (stream, ")");
527 return;
528 case greater_or_equal:
529 fprintf (stream, "(");
530 write_java_expression (stream, exp->val.args[0], false);
531 fprintf (stream, " >= ");
532 write_java_expression (stream, exp->val.args[1], false);
533 fprintf (stream, ")");
534 return;
535 case equal:
536 fprintf (stream, "(");
537 write_java_expression (stream, exp->val.args[0], false);
538 fprintf (stream, " == ");
539 write_java_expression (stream, exp->val.args[1], false);
540 fprintf (stream, ")");
541 return;
542 case not_equal:
543 fprintf (stream, "(");
544 write_java_expression (stream, exp->val.args[0], false);
545 fprintf (stream, " != ");
546 write_java_expression (stream, exp->val.args[1], false);
547 fprintf (stream, ")");
548 return;
549 case land:
550 fprintf (stream, "(");
551 write_java_expression (stream, exp->val.args[0], true);
552 fprintf (stream, " && ");
553 write_java_expression (stream, exp->val.args[1], true);
554 fprintf (stream, ")");
555 return;
556 case lor:
557 fprintf (stream, "(");
558 write_java_expression (stream, exp->val.args[0], true);
559 fprintf (stream, " || ");
560 write_java_expression (stream, exp->val.args[1], true);
561 fprintf (stream, ")");
562 return;
563 case qmop:
564 if (is_expression_boolean (exp->val.args[1])
565 && is_expression_boolean (exp->val.args[2]))
566 {
567 fprintf (stream, "(");
568 write_java_expression (stream, exp->val.args[0], true);
569 fprintf (stream, " ? ");
570 write_java_expression (stream, exp->val.args[1], true);
571 fprintf (stream, " : ");
572 write_java_expression (stream, exp->val.args[2], true);
573 fprintf (stream, ")");
574 return;
575 }
576 /*FALLTHROUGH*/
577 case var:
578 case mult:
579 case divide:
580 case module:
581 case plus:
582 case minus:
583 fprintf (stream, "(");
584 write_java_expression (stream, exp, false);
585 fprintf (stream, " != 0)");
586 return;
587 default:
588 abort ();
589 }
590 }
591 else
592 {
593 /* Emit a Java expression of type 'long'. */
594 switch (exp->operation)
595 {
596 case var:
597 fprintf (stream, "n");
598 return;
599 case num:
600 fprintf (stream, "%lu", exp->val.num);
601 return;
602 case mult:
603 fprintf (stream, "(");
604 write_java_expression (stream, exp->val.args[0], false);
605 fprintf (stream, " * ");
606 write_java_expression (stream, exp->val.args[1], false);
607 fprintf (stream, ")");
608 return;
609 case divide:
610 fprintf (stream, "(");
611 write_java_expression (stream, exp->val.args[0], false);
612 fprintf (stream, " / ");
613 write_java_expression (stream, exp->val.args[1], false);
614 fprintf (stream, ")");
615 return;
616 case module:
617 fprintf (stream, "(");
618 write_java_expression (stream, exp->val.args[0], false);
619 fprintf (stream, " %% ");
620 write_java_expression (stream, exp->val.args[1], false);
621 fprintf (stream, ")");
622 return;
623 case plus:
624 fprintf (stream, "(");
625 write_java_expression (stream, exp->val.args[0], false);
626 fprintf (stream, " + ");
627 write_java_expression (stream, exp->val.args[1], false);
628 fprintf (stream, ")");
629 return;
630 case minus:
631 fprintf (stream, "(");
632 write_java_expression (stream, exp->val.args[0], false);
633 fprintf (stream, " - ");
634 write_java_expression (stream, exp->val.args[1], false);
635 fprintf (stream, ")");
636 return;
637 case qmop:
638 fprintf (stream, "(");
639 write_java_expression (stream, exp->val.args[0], true);
640 fprintf (stream, " ? ");
641 write_java_expression (stream, exp->val.args[1], false);
642 fprintf (stream, " : ");
643 write_java_expression (stream, exp->val.args[2], false);
644 fprintf (stream, ")");
645 return;
646 case lnot:
647 case less_than:
648 case greater_than:
649 case less_or_equal:
650 case greater_or_equal:
651 case equal:
652 case not_equal:
653 case land:
654 case lor:
655 fprintf (stream, "(");
656 write_java_expression (stream, exp, true);
657 fprintf (stream, " ? 1 : 0)");
658 return;
659 default:
660 abort ();
661 }
662 }
663 }
664
665
666 /* Write the Java code for the ResourceBundle subclass to the given stream.
667 Note that we use fully qualified class names and no "import" statements,
668 because applications can have their own classes called X.Y.ResourceBundle
669 or X.Y.String. */
670 static void
write_java_code(FILE * stream,const char * class_name,message_list_ty * mlp,bool assume_java2)671 write_java_code (FILE *stream, const char *class_name, message_list_ty *mlp,
672 bool assume_java2)
673 {
674 const char *last_dot;
675 unsigned int plurals;
676 size_t j;
677
678 fprintf (stream,
679 "/* Automatically generated by GNU msgfmt. Do not modify! */\n");
680 last_dot = strrchr (class_name, '.');
681 if (last_dot != NULL)
682 {
683 fprintf (stream, "package ");
684 fwrite (class_name, 1, last_dot - class_name, stream);
685 fprintf (stream, ";\npublic class %s", last_dot + 1);
686 }
687 else
688 fprintf (stream, "public class %s", class_name);
689 fprintf (stream, " extends java.util.ResourceBundle {\n");
690
691 /* Determine whether there are plural messages. */
692 plurals = 0;
693 for (j = 0; j < mlp->nitems; j++)
694 if (mlp->item[j]->msgid_plural != NULL)
695 plurals++;
696
697 if (assume_java2)
698 {
699 unsigned int hashsize;
700 bool collisions;
701 struct table_item *table_items;
702 const char *table_eltype;
703
704 /* Determine the hash table size and whether it leads to collisions. */
705 hashsize = compute_hashsize (mlp, &collisions);
706
707 /* Determines which indices in the table contain a message. The others
708 are null. */
709 table_items = compute_table_items (mlp, hashsize);
710
711 /* Emit the table of pairs (msgid, msgstr). If there are plurals,
712 it is of type Object[], otherwise of type String[]. We use a static
713 code block because that makes less code: The Java compilers also
714 generate code for the 'null' entries, which is dumb. */
715 table_eltype = (plurals ? "java.lang.Object" : "java.lang.String");
716 fprintf (stream, " private static final %s[] table;\n", table_eltype);
717 fprintf (stream, " static {\n");
718 fprintf (stream, " %s[] t = new %s[%d];\n", table_eltype, table_eltype,
719 2 * hashsize);
720 for (j = 0; j < mlp->nitems; j++)
721 {
722 struct table_item *ti = &table_items[j];
723
724 fprintf (stream, " t[%d] = ", 2 * ti->index);
725 write_java_string (stream, ti->mp->msgid);
726 fprintf (stream, ";\n");
727 fprintf (stream, " t[%d] = ", 2 * ti->index + 1);
728 write_java_msgstr (stream, ti->mp);
729 fprintf (stream, ";\n");
730 }
731 fprintf (stream, " table = t;\n");
732 fprintf (stream, " }\n");
733
734 /* Emit the msgid_plural strings. Only used by msgunfmt. */
735 if (plurals)
736 {
737 bool first;
738 fprintf (stream, " public static final java.lang.String[] get_msgid_plural_table () {\n");
739 fprintf (stream, " return new java.lang.String[] { ");
740 first = true;
741 for (j = 0; j < mlp->nitems; j++)
742 {
743 struct table_item *ti = &table_items[j];
744 if (ti->mp->msgid_plural != NULL)
745 {
746 if (!first)
747 fprintf (stream, ", ");
748 write_java_string (stream, ti->mp->msgid_plural);
749 first = false;
750 }
751 }
752 fprintf (stream, " };\n");
753 fprintf (stream, " }\n");
754 }
755
756 if (plurals)
757 {
758 /* Emit the lookup function. It is a common subroutine for
759 handleGetObject and ngettext. */
760 fprintf (stream, " public java.lang.Object lookup (java.lang.String msgid) {\n");
761 write_lookup_code (stream, hashsize, collisions);
762 fprintf (stream, " }\n");
763 }
764
765 /* Emit the handleGetObject function. It is declared abstract in
766 ResourceBundle. It implements a local version of gettext. */
767 fprintf (stream, " public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {\n");
768 if (plurals)
769 {
770 fprintf (stream, " java.lang.Object value = lookup(msgid);\n");
771 fprintf (stream, " return (value instanceof java.lang.String[] ? ((java.lang.String[])value)[0] : value);\n");
772 }
773 else
774 write_lookup_code (stream, hashsize, collisions);
775 fprintf (stream, " }\n");
776
777 /* Emit the getKeys function. It is declared abstract in ResourceBundle.
778 The inner class is not avoidable. */
779 fprintf (stream, " public java.util.Enumeration getKeys () {\n");
780 fprintf (stream, " return\n");
781 fprintf (stream, " new java.util.Enumeration() {\n");
782 fprintf (stream, " private int idx = 0;\n");
783 fprintf (stream, " { while (idx < %d && table[idx] == null) idx += 2; }\n",
784 2 * hashsize);
785 fprintf (stream, " public boolean hasMoreElements () {\n");
786 fprintf (stream, " return (idx < %d);\n", 2 * hashsize);
787 fprintf (stream, " }\n");
788 fprintf (stream, " public java.lang.Object nextElement () {\n");
789 fprintf (stream, " java.lang.Object key = table[idx];\n");
790 fprintf (stream, " do idx += 2; while (idx < %d && table[idx] == null);\n",
791 2 * hashsize);
792 fprintf (stream, " return key;\n");
793 fprintf (stream, " }\n");
794 fprintf (stream, " };\n");
795 fprintf (stream, " }\n");
796 }
797 else
798 {
799 /* Java 1.1.x uses a different hash function. If compatibility with
800 this Java version is required, the hash table must be built at run time,
801 not at compile time. */
802 fprintf (stream, " private static final java.util.Hashtable table;\n");
803 fprintf (stream, " static {\n");
804 fprintf (stream, " java.util.Hashtable t = new java.util.Hashtable();\n");
805 for (j = 0; j < mlp->nitems; j++)
806 {
807 fprintf (stream, " t.put(");
808 write_java_string (stream, mlp->item[j]->msgid);
809 fprintf (stream, ",");
810 write_java_msgstr (stream, mlp->item[j]);
811 fprintf (stream, ");\n");
812 }
813 fprintf (stream, " table = t;\n");
814 fprintf (stream, " }\n");
815
816 /* Emit the msgid_plural strings. Only used by msgunfmt. */
817 if (plurals)
818 {
819 fprintf (stream, " public static final java.util.Hashtable get_msgid_plural_table () {\n");
820 fprintf (stream, " java.util.Hashtable p = new java.util.Hashtable();\n");
821 for (j = 0; j < mlp->nitems; j++)
822 if (mlp->item[j]->msgid_plural != NULL)
823 {
824 fprintf (stream, " p.put(");
825 write_java_string (stream, mlp->item[j]->msgid);
826 fprintf (stream, ",");
827 write_java_string (stream, mlp->item[j]->msgid_plural);
828 fprintf (stream, ");\n");
829 }
830 fprintf (stream, " return p;\n");
831 fprintf (stream, " }\n");
832 }
833
834 if (plurals)
835 {
836 /* Emit the lookup function. It is a common subroutine for
837 handleGetObject and ngettext. */
838 fprintf (stream, " public java.lang.Object lookup (java.lang.String msgid) {\n");
839 fprintf (stream, " return table.get(msgid);\n");
840 fprintf (stream, " }\n");
841 }
842
843 /* Emit the handleGetObject function. It is declared abstract in
844 ResourceBundle. It implements a local version of gettext. */
845 fprintf (stream, " public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {\n");
846 if (plurals)
847 {
848 fprintf (stream, " java.lang.Object value = table.get(msgid);\n");
849 fprintf (stream, " return (value instanceof java.lang.String[] ? ((java.lang.String[])value)[0] : value);\n");
850 }
851 else
852 fprintf (stream, " return table.get(msgid);\n");
853 fprintf (stream, " }\n");
854
855 /* Emit the getKeys function. It is declared abstract in
856 ResourceBundle. */
857 fprintf (stream, " public java.util.Enumeration getKeys () {\n");
858 fprintf (stream, " return table.keys();\n");
859 fprintf (stream, " }\n");
860 }
861
862 /* Emit the pluralEval function. It is a subroutine for ngettext. */
863 if (plurals)
864 {
865 message_ty *header_entry;
866 struct expression *plural;
867 unsigned long int nplurals;
868
869 header_entry = message_list_search (mlp, NULL, "");
870 extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
871 &plural, &nplurals);
872
873 fprintf (stream, " public static long pluralEval (long n) {\n");
874 fprintf (stream, " return ");
875 write_java_expression (stream, plural, false);
876 fprintf (stream, ";\n");
877 fprintf (stream, " }\n");
878 }
879
880 /* Emit the getParent function. It is a subroutine for ngettext. */
881 fprintf (stream, " public java.util.ResourceBundle getParent () {\n");
882 fprintf (stream, " return parent;\n");
883 fprintf (stream, " }\n");
884
885 fprintf (stream, "}\n");
886 }
887
888
889 int
msgdomain_write_java(message_list_ty * mlp,const char * canon_encoding,const char * resource_name,const char * locale_name,const char * directory,bool assume_java2)890 msgdomain_write_java (message_list_ty *mlp, const char *canon_encoding,
891 const char *resource_name, const char *locale_name,
892 const char *directory,
893 bool assume_java2)
894 {
895 int retval;
896 struct temp_dir *tmpdir;
897 int ndots;
898 char *class_name;
899 char **subdirs;
900 char *java_file_name;
901 FILE *java_file;
902 const char *java_sources[1];
903
904 /* If no entry for this resource/domain, don't even create the file. */
905 if (mlp->nitems == 0)
906 return 0;
907
908 /* Determine whether mlp has entries with context. */
909 {
910 bool has_context;
911 size_t j;
912
913 has_context = false;
914 for (j = 0; j < mlp->nitems; j++)
915 if (mlp->item[j]->msgctxt != NULL)
916 has_context = true;
917 if (has_context)
918 {
919 multiline_error (xstrdup (""),
920 xstrdup (_("\
921 message catalog has context dependent translations\n\
922 but the Java ResourceBundle format doesn't support contexts\n")));
923 return 1;
924 }
925 }
926
927 retval = 1;
928
929 /* Convert the messages to Unicode. */
930 iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
931
932 /* Create a temporary directory where we can put the Java file. */
933 tmpdir = create_temp_dir ("msg", NULL, false);
934 if (tmpdir == NULL)
935 goto quit1;
936
937 /* Assign a default value to the resource name. */
938 if (resource_name == NULL)
939 resource_name = "Messages";
940
941 /* Prepare the list of subdirectories. */
942 ndots = check_resource_name (resource_name);
943 if (ndots < 0)
944 {
945 error (0, 0, _("not a valid Java class name: %s"), resource_name);
946 goto quit2;
947 }
948
949 if (locale_name != NULL)
950 class_name = xasprintf ("%s_%s", resource_name, locale_name);
951 else
952 class_name = xstrdup (resource_name);
953
954 subdirs = (ndots > 0 ? (char **) xallocsa (ndots * sizeof (char *)) : NULL);
955 {
956 const char *p;
957 const char *last_dir;
958 int i;
959
960 last_dir = tmpdir->dir_name;
961 p = resource_name;
962 for (i = 0; i < ndots; i++)
963 {
964 const char *q = strchr (p, '.');
965 size_t n = q - p;
966 char *part = (char *) xallocsa (n + 1);
967 memcpy (part, p, n);
968 part[n] = '\0';
969 subdirs[i] = concatenated_pathname (last_dir, part, NULL);
970 freesa (part);
971 last_dir = subdirs[i];
972 p = q + 1;
973 }
974
975 if (locale_name != NULL)
976 {
977 char *suffix = xasprintf ("_%s.java", locale_name);
978 java_file_name = concatenated_pathname (last_dir, p, suffix);
979 free (suffix);
980 }
981 else
982 java_file_name = concatenated_pathname (last_dir, p, ".java");
983 }
984
985 /* Create the subdirectories. This is needed because some older Java
986 compilers verify that the source of class A.B.C really sits in a
987 directory whose name ends in /A/B. */
988 {
989 int i;
990
991 for (i = 0; i < ndots; i++)
992 {
993 register_temp_subdir (tmpdir, subdirs[i]);
994 if (mkdir (subdirs[i], S_IRUSR | S_IWUSR | S_IXUSR) < 0)
995 {
996 error (0, errno, _("failed to create \"%s\""), subdirs[i]);
997 unregister_temp_subdir (tmpdir, subdirs[i]);
998 goto quit3;
999 }
1000 }
1001 }
1002
1003 /* Create the Java file. */
1004 register_temp_file (tmpdir, java_file_name);
1005 java_file = fopen_temp (java_file_name, "w");
1006 if (java_file == NULL)
1007 {
1008 error (0, errno, _("failed to create \"%s\""), java_file_name);
1009 unregister_temp_file (tmpdir, java_file_name);
1010 goto quit3;
1011 }
1012
1013 write_java_code (java_file, class_name, mlp, assume_java2);
1014
1015 if (fwriteerror_temp (java_file))
1016 {
1017 error (0, errno, _("error while writing \"%s\" file"), java_file_name);
1018 goto quit3;
1019 }
1020
1021 /* Compile the Java file to a .class file.
1022 directory must be non-NULL, because when the -d option is omitted, the
1023 Java compilers create the class files in the source file's directory -
1024 which is in a temporary directory in our case. */
1025 java_sources[0] = java_file_name;
1026 if (compile_java_class (java_sources, 1, NULL, 0, "1.3", "1.1", directory,
1027 true, false, true, verbose))
1028 {
1029 error (0, 0, _("\
1030 compilation of Java class failed, please try --verbose or set $JAVAC"));
1031 goto quit3;
1032 }
1033
1034 retval = 0;
1035
1036 quit3:
1037 {
1038 int i;
1039 free (java_file_name);
1040 for (i = 0; i < ndots; i++)
1041 free (subdirs[i]);
1042 }
1043 freesa (subdirs);
1044 free (class_name);
1045 quit2:
1046 cleanup_temp_dir (tmpdir);
1047 quit1:
1048 return retval;
1049 }
1050