a21c002b2d775dcfe36b386c2eb76a3b04e81d28
[platform/upstream/gettext.git] / gettext-tools / src / write-java.c
1 /* Writing Java ResourceBundles.
2    Copyright (C) 2001-2003, 2005-2010, 2015 Free Software Foundation,
3    Inc.
4    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
5
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.
10
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.
15
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/>.  */
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 #include "c-ctype.h"
58 #include "error.h"
59 #include "xerror.h"
60 #include "xvasprintf.h"
61 #include "javacomp.h"
62 #include "message.h"
63 #include "msgfmt.h"
64 #include "msgl-iconv.h"
65 #include "plural-exp.h"
66 #include "po-charset.h"
67 #include "xalloc.h"
68 #include "xmalloca.h"
69 #include "minmax.h"
70 #include "concat-filename.h"
71 #include "fwriteerror.h"
72 #include "clean-temp.h"
73 #include "unistr.h"
74 #include "gettext.h"
75
76 #define _(str) gettext (str)
77
78
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.  */
82 static int
83 check_resource_name (const char *name)
84 {
85   int ndots = 0;
86   const char *p = name;
87
88   for (;;)
89     {
90       /* First character, see Character.isJavaIdentifierStart.  */
91       if (!(c_isalpha (*p) || (*p == '$') || (*p == '_')))
92         return -1;
93       /* Following characters, see Character.isJavaIdentifierPart.  */
94       do
95         p++;
96       while (c_isalpha (*p) || (*p == '$') || (*p == '_') || c_isdigit (*p));
97       if (*p == '\0')
98         break;
99       if (*p != '.')
100         return -1;
101       p++;
102       ndots++;
103     }
104   return ndots;
105 }
106
107
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.  */
115 static unsigned int
116 string_hashcode (const char *str)
117 {
118   const char *str_limit = str + strlen (str);
119   int hash = 0;
120   while (str < str_limit)
121     {
122       ucs4_t uc;
123       str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
124       if (uc < 0x10000)
125         /* Single UCS-2 'char'.  */
126         hash = 31 * hash + uc;
127       else
128         {
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;
134         }
135     }
136   return hash & 0x7fffffff;
137 }
138
139
140 /* Return the Java hash code of a (msgctxt, msgid) pair mod 2^31.  */
141 static unsigned int
142 msgid_hashcode (const char *msgctxt, const char *msgid)
143 {
144   if (msgctxt == NULL)
145     return string_hashcode (msgid);
146   else
147     {
148       size_t msgctxt_len = strlen (msgctxt);
149       size_t msgid_len = strlen (msgid);
150       size_t combined_len = msgctxt_len + 1 + msgid_len;
151       char *combined;
152       unsigned int result;
153
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);
158
159       result = string_hashcode (combined);
160
161       freea (combined);
162
163       return result;
164     }
165 }
166
167
168 /* Compute a good hash table size for the given set of msgids.  */
169 static unsigned int
170 compute_hashsize (message_list_ty *mlp, bool *collisionp)
171 {
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;
182   size_t j;
183
184   for (j = 0; j < n; j++)
185     hashcodes[j] = msgid_hashcode (mlp->item[j]->msgctxt, mlp->item[j]->msgid);
186
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.  */
192   best_hashsize = 0;
193   best_score = UINT_MAX;
194   for (hashsize = n; hashsize <= XXN * n; hashsize++)
195     {
196       char *bitmap;
197       unsigned int score;
198
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)
203         break;
204
205       bitmap = XNMALLOC (hashsize, char);
206       memset (bitmap, 0, hashsize);
207
208       score = 0;
209       for (j = 0; j < n; j++)
210         {
211           unsigned int idx = hashcodes[j] % hashsize;
212
213           if (bitmap[idx] != 0)
214             {
215               /* Collision.  Cannot deal with it if hashsize is even.  */
216               if ((hashsize % 2) == 0)
217                 /* Try next hashsize.  */
218                 goto bad_hashsize;
219               else
220                 {
221                   unsigned int idx0 = idx;
222                   unsigned int incr = 1 + (hashcodes[j] % (hashsize - 2));
223                   score += 2;   /* Big penalty for the additional division */
224                   do
225                     {
226                       score++;  /* Small penalty for each loop round */
227                       idx += incr;
228                       if (idx >= hashsize)
229                         idx -= hashsize;
230                       if (idx == idx0)
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
234                            hashsize.  */
235                         goto bad_hashsize;
236                     }
237                   while (bitmap[idx] != 0);
238                 }
239             }
240           bitmap[idx] = 1;
241         }
242
243       /* Big hashsize also gives a penalty.  */
244       score = XXS * score + hashsize;
245
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)
251         {
252           unsigned int incr;
253
254           /* Since the set { idx0, idx0 + incr, ... } depends only on idx0
255              and gcd(hashsize,incr), we only need to conside incr that
256              divides hashsize.  */
257           for (incr = 1; incr <= hashsize / 2; incr++)
258             if ((hashsize % incr) == 0)
259               {
260                 unsigned int idx0;
261
262                 for (idx0 = 0; idx0 < incr; idx0++)
263                   {
264                     bool full = true;
265                     unsigned int idx;
266
267                     for (idx = idx0; idx < hashsize; idx += incr)
268                       if (bitmap[idx] == 0)
269                         {
270                           full = false;
271                           break;
272                         }
273                     if (full)
274                       /* A whole round is occupied.  */
275                       goto bad_hashsize;
276                   }
277               }
278         }
279
280       if (false)
281         bad_hashsize:
282         score = UINT_MAX;
283
284       free (bitmap);
285
286       if (score < best_score)
287         {
288           best_score = score;
289           best_hashsize = hashsize;
290         }
291     }
292   if (best_hashsize == 0 || best_score < best_hashsize)
293     abort ();
294
295   freea (hashcodes);
296
297   /* There are collisions if and only if best_score > best_hashsize.  */
298   *collisionp = (best_score > best_hashsize);
299   return best_hashsize;
300 }
301
302
303 struct table_item { unsigned int index; message_ty *mp; };
304
305 static int
306 compare_index (const void *pval1, const void *pval2)
307 {
308   return (int)((const struct table_item *) pval1)->index
309          - (int)((const struct table_item *) pval2)->index;
310 }
311
312 /* Compute the list of messages and table indices, sorted according to the
313    indices.  */
314 static struct table_item *
315 compute_table_items (message_list_ty *mlp, unsigned int hashsize)
316 {
317   unsigned int n = mlp->nitems;
318   struct table_item *arr = XNMALLOC (n, struct table_item);
319   char *bitmap;
320   size_t j;
321
322   bitmap = XNMALLOC (hashsize, char);
323   memset (bitmap, 0, hashsize);
324
325   for (j = 0; j < n; j++)
326     {
327       unsigned int hashcode =
328         msgid_hashcode (mlp->item[j]->msgctxt, mlp->item[j]->msgid);
329       unsigned int idx = hashcode % hashsize;
330
331       if (bitmap[idx] != 0)
332         {
333           unsigned int incr = 1 + (hashcode % (hashsize - 2));
334           do
335             {
336               idx += incr;
337               if (idx >= hashsize)
338                 idx -= hashsize;
339             }
340           while (bitmap[idx] != 0);
341         }
342       bitmap[idx] = 1;
343
344       arr[j].index = idx;
345       arr[j].mp = mlp->item[j];
346     }
347
348   free (bitmap);
349
350   qsort (arr, n, sizeof (arr[0]), compare_index);
351
352   return arr;
353 }
354
355
356 /* Write a string in Java Unicode notation to the given stream.  */
357 static void
358 write_java_string (FILE *stream, const char *str)
359 {
360   static const char hexdigit[] = "0123456789abcdef";
361   const char *str_limit = str + strlen (str);
362
363   fprintf (stream, "\"");
364   while (str < str_limit)
365     {
366       ucs4_t uc;
367       str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
368       if (uc < 0x10000)
369         {
370           /* Single UCS-2 'char'.  */
371           if (uc == 0x000a)
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);
381           else
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]);
385         }
386       else
387         {
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]);
397         }
398     }
399   fprintf (stream, "\"");
400 }
401
402
403 /* Write a (msgctxt, msgid) pair as a string in Java Unicode notation to the
404    given stream.  */
405 static void
406 write_java_msgid (FILE *stream, message_ty *mp)
407 {
408   const char *msgctxt = mp->msgctxt;
409   const char *msgid = mp->msgid;
410
411   if (msgctxt == NULL)
412     write_java_string (stream, msgid);
413   else
414     {
415       size_t msgctxt_len = strlen (msgctxt);
416       size_t msgid_len = strlen (msgid);
417       size_t combined_len = msgctxt_len + 1 + msgid_len;
418       char *combined;
419
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);
424
425       write_java_string (stream, combined);
426
427       freea (combined);
428     }
429 }
430
431
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.  */
435 static void
436 write_java_msgstr (FILE *stream, message_ty *mp)
437 {
438   if (mp->msgid_plural != NULL)
439     {
440       bool first;
441       const char *p;
442
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)
447         {
448           if (!first)
449             fprintf (stream, ", ");
450           write_java_string (stream, p);
451         }
452       fprintf (stream, " }");
453     }
454   else
455     {
456       if (mp->msgstr_len != strlen (mp->msgstr) + 1)
457         abort ();
458
459       write_java_string (stream, mp->msgstr);
460     }
461 }
462
463
464 /* Writes the body of the function which returns the local value for a key
465    named 'msgid'.  */
466 static void
467 write_lookup_code (FILE *stream, unsigned int hashsize, bool collisions)
468 {
469   fprintf (stream, "    int hash_val = msgid.hashCode() & 0x7fffffff;\n");
470   fprintf (stream, "    int idx = (hash_val %% %d) << 1;\n", hashsize);
471   if (collisions)
472     {
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",
481                hashsize - 2);
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");
492     }
493   else
494     {
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");
499     }
500 }
501
502
503 /* Tests whether a plural expression, evaluated according to the C rules,
504    can only produce the values 0 and 1.  */
505 static bool
506 is_expression_boolean (struct expression *exp)
507 {
508   switch (exp->operation)
509     {
510     case var:
511     case mult:
512     case divide:
513     case module:
514     case plus:
515     case minus:
516       return false;
517     case lnot:
518     case less_than:
519     case greater_than:
520     case less_or_equal:
521     case greater_or_equal:
522     case equal:
523     case not_equal:
524     case land:
525     case lor:
526       return true;
527     case num:
528       return (exp->val.num == 0 || exp->val.num == 1);
529     case qmop:
530       return is_expression_boolean (exp->val.args[1])
531              && is_expression_boolean (exp->val.args[2]);
532     default:
533       abort ();
534     }
535 }
536
537
538 /* Write Java code that evaluates a plural expression according to the C rules.
539    The variable is called 'n'.  */
540 static void
541 write_java_expression (FILE *stream, const struct expression *exp, bool as_boolean)
542 {
543   /* We use parentheses everywhere.  This frees us from tracking the priority
544      of arithmetic operators.  */
545   if (as_boolean)
546     {
547       /* Emit a Java expression of type 'boolean'.  */
548       switch (exp->operation)
549         {
550         case num:
551           fprintf (stream, "%s", exp->val.num ? "true" : "false");
552           return;
553         case lnot:
554           fprintf (stream, "(!");
555           write_java_expression (stream, exp->val.args[0], true);
556           fprintf (stream, ")");
557           return;
558         case less_than:
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, ")");
564           return;
565         case greater_than:
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, ")");
571           return;
572         case less_or_equal:
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, ")");
578           return;
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, ")");
585           return;
586         case equal:
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, ")");
592           return;
593         case not_equal:
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, ")");
599           return;
600         case land:
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, ")");
606           return;
607         case lor:
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, ")");
613           return;
614         case qmop:
615           if (is_expression_boolean (exp->val.args[1])
616               && is_expression_boolean (exp->val.args[2]))
617             {
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, ")");
625               return;
626             }
627           /*FALLTHROUGH*/
628         case var:
629         case mult:
630         case divide:
631         case module:
632         case plus:
633         case minus:
634           fprintf (stream, "(");
635           write_java_expression (stream, exp, false);
636           fprintf (stream, " != 0)");
637           return;
638         default:
639           abort ();
640         }
641     }
642   else
643     {
644       /* Emit a Java expression of type 'long'.  */
645       switch (exp->operation)
646         {
647         case var:
648           fprintf (stream, "n");
649           return;
650         case num:
651           fprintf (stream, "%lu", exp->val.num);
652           return;
653         case mult:
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, ")");
659           return;
660         case divide:
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, ")");
666           return;
667         case module:
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, ")");
673           return;
674         case plus:
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, ")");
680           return;
681         case minus:
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, ")");
687           return;
688         case qmop:
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, ")");
696           return;
697         case lnot:
698         case less_than:
699         case greater_than:
700         case less_or_equal:
701         case greater_or_equal:
702         case equal:
703         case not_equal:
704         case land:
705         case lor:
706           fprintf (stream, "(");
707           write_java_expression (stream, exp, true);
708           fprintf (stream, " ? 1 : 0)");
709           return;
710         default:
711           abort ();
712         }
713     }
714 }
715
716
717 /* Write the Java initialization statements for the Java 1.1.x case,
718    for items j, start_index <= j < end_index.  */
719 static void
720 write_java1_init_statements (FILE *stream, message_list_ty *mlp,
721                              size_t start_index, size_t end_index)
722 {
723   size_t j;
724
725   for (j = start_index; j < end_index; j++)
726     {
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");
732     }
733 }
734
735
736 /* Write the Java initialization statements for the Java 2 case,
737    for items j, start_index <= j < end_index.  */
738 static void
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)
742 {
743   size_t j;
744
745   for (j = start_index; j < end_index; j++)
746     {
747       const struct table_item *ti = &table_items[j];
748
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");
755     }
756 }
757
758
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
762    or X.Y.String.  */
763 static void
764 write_java_code (FILE *stream, const char *class_name, message_list_ty *mlp,
765                  bool assume_java2)
766 {
767   const char *last_dot;
768   unsigned int plurals;
769   size_t j;
770
771   fprintf (stream,
772            "/* Automatically generated by GNU msgfmt.  Do not modify!  */\n");
773   last_dot = strrchr (class_name, '.');
774   if (last_dot != NULL)
775     {
776       fprintf (stream, "package ");
777       fwrite (class_name, 1, last_dot - class_name, stream);
778       fprintf (stream, ";\npublic class %s", last_dot + 1);
779     }
780   else
781     fprintf (stream, "public class %s", class_name);
782   fprintf (stream, " extends java.util.ResourceBundle {\n");
783
784   /* Determine whether there are plural messages.  */
785   plurals = 0;
786   for (j = 0; j < mlp->nitems; j++)
787     if (mlp->item[j]->msgid_plural != NULL)
788       plurals++;
789
790   if (assume_java2)
791     {
792       unsigned int hashsize;
793       bool collisions;
794       struct table_item *table_items;
795       const char *table_eltype;
796
797       /* Determine the hash table size and whether it leads to collisions.  */
798       hashsize = compute_hashsize (mlp, &collisions);
799
800       /* Determines which indices in the table contain a message.  The others
801          are null.  */
802       table_items = compute_table_items (mlp, hashsize);
803
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);
810       {
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
817            1000.  */
818         const size_t max_items_per_method = 1000;
819
820         if (mlp->nitems > max_items_per_method)
821           {
822             unsigned int k;
823             size_t start_j;
824             size_t end_j;
825
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)
829               {
830                 fprintf (stream, "  static void clinit_part_%u (%s[] t) {\n",
831                          k, table_eltype);
832                 write_java2_init_statements (stream, mlp, table_items,
833                                              start_j, MIN (end_j, mlp->nitems));
834                 fprintf (stream, "  }\n");
835               }
836           }
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)
841           {
842             unsigned int k;
843             size_t start_j;
844
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);
849           }
850         else
851           write_java2_init_statements (stream, mlp, table_items,
852                                        0, mlp->nitems);
853         fprintf (stream, "    table = t;\n");
854         fprintf (stream, "  }\n");
855       }
856
857       /* Emit the msgid_plural strings.  Only used by msgunfmt.  */
858       if (plurals)
859         {
860           bool first;
861           fprintf (stream, "  public static final java.lang.String[] get_msgid_plural_table () {\n");
862           fprintf (stream, "    return new java.lang.String[] { ");
863           first = true;
864           for (j = 0; j < mlp->nitems; j++)
865             {
866               struct table_item *ti = &table_items[j];
867               if (ti->mp->msgid_plural != NULL)
868                 {
869                   if (!first)
870                     fprintf (stream, ", ");
871                   write_java_string (stream, ti->mp->msgid_plural);
872                   first = false;
873                 }
874             }
875           fprintf (stream, " };\n");
876           fprintf (stream, "  }\n");
877         }
878
879       if (plurals)
880         {
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");
886         }
887
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");
891       if (plurals)
892         {
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");
895         }
896       else
897         write_lookup_code (stream, hashsize, collisions);
898       fprintf (stream, "  }\n");
899
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",
907                2 * hashsize);
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",
914                2 * hashsize);
915       fprintf (stream, "          return key;\n");
916       fprintf (stream, "        }\n");
917       fprintf (stream, "      };\n");
918       fprintf (stream, "  }\n");
919     }
920   else
921     {
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");
926       {
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
933            1500.  */
934         const size_t max_items_per_method = 1500;
935
936         if (mlp->nitems > max_items_per_method)
937           {
938             unsigned int k;
939             size_t start_j;
940             size_t end_j;
941
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)
945               {
946                 fprintf (stream, "  static void clinit_part_%u (java.util.Hashtable t) {\n",
947                          k);
948                 write_java1_init_statements (stream, mlp,
949                                              start_j, MIN (end_j, mlp->nitems));
950                 fprintf (stream, "  }\n");
951               }
952           }
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)
956           {
957             unsigned int k;
958             size_t start_j;
959
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);
964           }
965         else
966           write_java1_init_statements (stream, mlp, 0, mlp->nitems);
967         fprintf (stream, "    table = t;\n");
968         fprintf (stream, "  }\n");
969       }
970
971       /* Emit the msgid_plural strings.  Only used by msgunfmt.  */
972       if (plurals)
973         {
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)
978               {
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");
984               }
985           fprintf (stream, "    return p;\n");
986           fprintf (stream, "  }\n");
987         }
988
989       if (plurals)
990         {
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");
996         }
997
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");
1001       if (plurals)
1002         {
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");
1005         }
1006       else
1007         fprintf (stream, "    return table.get(msgid);\n");
1008       fprintf (stream, "  }\n");
1009
1010       /* Emit the getKeys function.  It is declared abstract in
1011          ResourceBundle.  */
1012       fprintf (stream, "  public java.util.Enumeration getKeys () {\n");
1013       fprintf (stream, "    return table.keys();\n");
1014       fprintf (stream, "  }\n");
1015     }
1016
1017   /* Emit the pluralEval function.  It is a subroutine for ngettext.  */
1018   if (plurals)
1019     {
1020       message_ty *header_entry;
1021       const struct expression *plural;
1022       unsigned long int nplurals;
1023
1024       header_entry = message_list_search (mlp, NULL, "");
1025       extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
1026                                  &plural, &nplurals);
1027
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");
1033     }
1034
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");
1039
1040   fprintf (stream, "}\n");
1041 }
1042
1043
1044 int
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,
1048                       bool assume_java2,
1049                       bool output_source)
1050 {
1051   int retval;
1052   struct temp_dir *tmpdir;
1053   int ndots;
1054   char *class_name;
1055   char **subdirs;
1056   char *java_file_name;
1057   FILE *java_file;
1058   const char *java_sources[1];
1059   const char *source_dir_name;
1060
1061   /* If no entry for this resource/domain, don't even create the file.  */
1062   if (mlp->nitems == 0)
1063     return 0;
1064
1065   retval = 1;
1066
1067   /* Convert the messages to Unicode.  */
1068   iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
1069
1070   if (output_source)
1071     {
1072       tmpdir = NULL;
1073       source_dir_name = directory;
1074     }
1075   else
1076     {
1077       /* Create a temporary directory where we can put the Java file.  */
1078       tmpdir = create_temp_dir ("msg", NULL, false);
1079       if (tmpdir == NULL)
1080         goto quit1;
1081       source_dir_name = tmpdir->dir_name;
1082     }
1083
1084   /* Assign a default value to the resource name.  */
1085   if (resource_name == NULL)
1086     resource_name = "Messages";
1087
1088   /* Prepare the list of subdirectories.  */
1089   ndots = check_resource_name (resource_name);
1090   if (ndots < 0)
1091     {
1092       error (0, 0, _("not a valid Java class name: %s"), resource_name);
1093       goto quit2;
1094     }
1095
1096   if (locale_name != NULL)
1097     class_name = xasprintf ("%s_%s", resource_name, locale_name);
1098   else
1099     class_name = xstrdup (resource_name);
1100
1101   subdirs = (ndots > 0 ? (char **) xmalloca (ndots * sizeof (char *)) : NULL);
1102   {
1103     const char *p;
1104     const char *last_dir;
1105     int i;
1106
1107     last_dir = source_dir_name;
1108     p = resource_name;
1109     for (i = 0; i < ndots; i++)
1110       {
1111         const char *q = strchr (p, '.');
1112         size_t n = q - p;
1113         char *part = (char *) xmalloca (n + 1);
1114         memcpy (part, p, n);
1115         part[n] = '\0';
1116         subdirs[i] = xconcatenated_filename (last_dir, part, NULL);
1117         freea (part);
1118         last_dir = subdirs[i];
1119         p = q + 1;
1120       }
1121
1122     if (locale_name != NULL)
1123       {
1124         char *suffix = xasprintf ("_%s.java", locale_name);
1125         java_file_name = xconcatenated_filename (last_dir, p, suffix);
1126         free (suffix);
1127       }
1128     else
1129       java_file_name = xconcatenated_filename (last_dir, p, ".java");
1130   }
1131
1132   /* If OUTPUT_SOURCE, write the Java file in DIRECTORY and return.  */
1133   if (output_source)
1134     {
1135       int i;
1136
1137       for (i = 0; i < ndots; i++)
1138         {
1139           if (mkdir (subdirs[i], S_IRUSR | S_IWUSR | S_IXUSR) < 0)
1140             {
1141               error (0, errno, _("failed to create \"%s\""), subdirs[i]);
1142               goto quit3;
1143             }
1144         }
1145
1146       java_file = fopen (java_file_name, "w");
1147       if (java_file == NULL)
1148         {
1149           error (0, errno, _("failed to create \"%s\""), java_file_name);
1150           goto quit3;
1151         }
1152
1153       write_java_code (java_file, class_name, mlp, assume_java2);
1154
1155       if (fwriteerror (java_file))
1156         {
1157           error (0, errno, _("error while writing \"%s\" file"),
1158                  java_file_name);
1159           goto quit3;
1160         }
1161
1162       retval = 0;
1163       goto quit3;
1164     }
1165
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.  */
1169   {
1170     int i;
1171
1172     for (i = 0; i < ndots; i++)
1173       {
1174         register_temp_subdir (tmpdir, subdirs[i]);
1175         if (mkdir (subdirs[i], S_IRUSR | S_IWUSR | S_IXUSR) < 0)
1176           {
1177             error (0, errno, _("failed to create \"%s\""), subdirs[i]);
1178             unregister_temp_subdir (tmpdir, subdirs[i]);
1179             goto quit3;
1180           }
1181       }
1182   }
1183
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)
1188     {
1189       error (0, errno, _("failed to create \"%s\""), java_file_name);
1190       unregister_temp_file (tmpdir, java_file_name);
1191       goto quit3;
1192     }
1193
1194   write_java_code (java_file, class_name, mlp, assume_java2);
1195
1196   if (fwriteerror_temp (java_file))
1197     {
1198       error (0, errno, _("error while writing \"%s\" file"), java_file_name);
1199       goto quit3;
1200     }
1201
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))
1209     {
1210       if (!verbose)
1211         error (0, 0, _("\
1212 compilation of Java class failed, please try --verbose or set $JAVAC"));
1213       else
1214         error (0, 0, _("\
1215 compilation of Java class failed, please try to set $JAVAC"));
1216       goto quit3;
1217     }
1218
1219   retval = 0;
1220
1221  quit3:
1222   {
1223     int i;
1224     free (java_file_name);
1225     for (i = 0; i < ndots; i++)
1226       free (subdirs[i]);
1227   }
1228   freea (subdirs);
1229   free (class_name);
1230  quit2:
1231   if (tmpdir != NULL)
1232     cleanup_temp_dir (tmpdir);
1233  quit1:
1234   return retval;
1235 }