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