added -J option to preserve unmodified files in preexisting jarfile
[org.ibex.tool.git] / src / org / eclipse / jdt / internal / compiler / util / Util.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package org.eclipse.jdt.internal.compiler.util;
12
13 import java.io.BufferedInputStream;
14 import java.io.ByteArrayInputStream;
15 import java.io.File;
16 import java.io.FileInputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.InputStreamReader;
20 import java.util.Locale;
21 import java.util.MissingResourceException;
22 import java.util.ResourceBundle;
23 import java.util.zip.ZipEntry;
24 import java.util.zip.ZipFile;
25 import org.eclipse.jdt.core.compiler.CharOperation;
26
27 public class Util implements SuffixConstants {
28
29         public interface Displayable {
30                 String displayString(Object o);
31         }
32         static {
33                 relocalize();
34         }
35
36         /* Bundle containing messages */
37         protected static ResourceBundle bundle;
38         private final static String bundleName =
39                 "org.eclipse.jdt.internal.compiler.util.messages"; //$NON-NLS-1$
40         private static final int DEFAULT_READING_SIZE = 8192;
41         
42         private final static char[] DOUBLE_QUOTES = "''".toCharArray(); //$NON-NLS-1$
43
44         public static String LINE_SEPARATOR = System.getProperty("line.separator"); //$NON-NLS-1$
45         public static char[] LINE_SEPARATOR_CHARS = LINE_SEPARATOR.toCharArray();
46         private final static char[] SINGLE_QUOTE = "'".toCharArray(); //$NON-NLS-1$
47         
48         /**
49          * Lookup the message with the given ID in this catalog 
50          */
51         public static String bind(String id) {
52                 return bind(id, (String[]) null);
53         }
54         /**
55          * Lookup the message with the given ID in this catalog and bind its
56          * substitution locations with the given string.
57          */
58         public static String bind(String id, String argument) {
59                 return bind(id, new String[] { argument });
60         }
61         /**
62          * Lookup the message with the given ID in this catalog and bind its
63          * substitution locations with the given strings.
64          */
65         public static String bind(String id, String argument1, String argument2) {
66                 return bind(id, new String[] { argument1, argument2 });
67         }
68         /**
69          * Lookup the message with the given ID in this catalog and bind its
70          * substitution locations with the given string values.
71          */
72         public static String bind(String id, String[] arguments) {
73                 if (id == null)
74                         return "No message available"; //$NON-NLS-1$
75                 String message = null;
76                 try {
77                         message = bundle.getString(id);
78                 } catch (MissingResourceException e) {
79                         // If we got an exception looking for the message, fail gracefully by just returning
80                         // the id we were looking for.  In most cases this is semi-informative so is not too bad.
81                         return "Missing message: " + id + " in: " + bundleName; //$NON-NLS-2$ //$NON-NLS-1$
82                 }
83                 return bindMessage(message, arguments);
84         }
85         /**
86          * Bind some message with given string values.
87          */
88         public static String bindMessage(String message, String[] arguments) {
89                 // for compatibility with MessageFormat which eliminates double quotes in original message
90                 char[] messageWithNoDoubleQuotes =
91                         CharOperation.replace(message.toCharArray(), DOUBLE_QUOTES, SINGLE_QUOTE);
92         
93                 if (arguments == null) return new String(messageWithNoDoubleQuotes);
94         
95                 int length = messageWithNoDoubleQuotes.length;
96                 int start = 0;
97                 int end = length;
98                 StringBuffer output = null;
99                 while (true) {
100                         if ((end = CharOperation.indexOf('{', messageWithNoDoubleQuotes, start)) > -1) {
101                                 if (output == null) output = new StringBuffer(length+arguments.length*20);
102                                 output.append(messageWithNoDoubleQuotes, start, end - start);
103                                 if ((start = CharOperation.indexOf('}', messageWithNoDoubleQuotes, end + 1)) > -1) {
104                                         int index = -1;
105                                         String argId = new String(messageWithNoDoubleQuotes, end + 1, start - end - 1);
106                                         try {
107                                                 index = Integer.parseInt(argId);
108                                                 if (arguments[index] == null) {
109                                                         output.append('{').append(argId).append('}'); // leave parameter in since no better arg '{0}'
110                                                 } else {
111                                                         output.append(arguments[index]);
112                                                 }                                               
113                                         } catch (NumberFormatException nfe) { // could be nested message ID {compiler.name}
114                                                 boolean done = false;
115                                                 String argMessage = null;
116                                                 try {
117                                                         argMessage = bundle.getString(argId);
118                                                         output.append(argMessage);
119                                                         done = true;
120                                                 } catch (MissingResourceException e) {
121                                                         // unable to bind argument, ignore (will leave argument in)
122                                                 }
123                                                 if (!done) output.append(messageWithNoDoubleQuotes, end + 1, start - end);
124                                         } catch (ArrayIndexOutOfBoundsException e) {
125                                                 output.append("{missing " + Integer.toString(index) + "}"); //$NON-NLS-2$ //$NON-NLS-1$
126                                         }
127                                         start++;
128                                 } else {
129                                         output.append(messageWithNoDoubleQuotes, end, length);
130                                         break;
131                                 }
132                         } else {
133                                 if (output == null) return new String(messageWithNoDoubleQuotes);
134                                 output.append(messageWithNoDoubleQuotes, start, length - start);
135                                 break;
136                         }
137                 }
138                 return output.toString();
139         }
140         /**
141          * Returns the given bytes as a char array using a given encoding (null means platform default).
142          */
143         public static char[] bytesToChar(byte[] bytes, String encoding) throws IOException {
144
145                 return getInputStreamAsCharArray(new ByteArrayInputStream(bytes), bytes.length, encoding);
146
147         }
148         /**
149          * Returns the contents of the given file as a byte array.
150          * @throws IOException if a problem occured reading the file.
151          */
152         public static byte[] getFileByteContent(File file) throws IOException {
153                 InputStream stream = null;
154                 try {
155                         stream = new BufferedInputStream(new FileInputStream(file));
156                         return getInputStreamAsByteArray(stream, (int) file.length());
157                 } finally {
158                         if (stream != null) {
159                                 try {
160                                         stream.close();
161                                 } catch (IOException e) {
162                                         // ignore
163                                 }
164                         }
165                 }
166         }
167         /**
168          * Returns the contents of the given file as a char array.
169          * When encoding is null, then the platform default one is used
170          * @throws IOException if a problem occured reading the file.
171          */
172         public static char[] getFileCharContent(File file, String encoding) throws IOException {
173                 InputStream stream = null;
174                 try {
175                         stream = new BufferedInputStream(new FileInputStream(file));
176                         return getInputStreamAsCharArray(stream, (int) file.length(), encoding);
177                 } finally {
178                         if (stream != null) {
179                                 try {
180                                         stream.close();
181                                 } catch (IOException e) {
182                                         // ignore
183                                 }
184                         }
185                 }
186         }
187         /*
188          * NIO support to get input stream as byte array.
189          * Not used as with JDK 1.4.2 this support is slower than standard IO one...
190          * Keep it as comment for future in case of next JDK versions improve performance
191          * in this area...
192          *
193         public static byte[] getInputStreamAsByteArray(FileInputStream stream, int length)
194                 throws IOException {
195
196                 FileChannel channel = stream.getChannel();
197                 int size = (int)channel.size();
198                 if (length >= 0 && length < size) size = length;
199                 byte[] contents = new byte[size];
200                 ByteBuffer buffer = ByteBuffer.wrap(contents);
201                 channel.read(buffer);
202                 return contents;
203         }
204         */
205         /**
206          * Returns the given input stream's contents as a byte array.
207          * If a length is specified (ie. if length != -1), only length bytes
208          * are returned. Otherwise all bytes in the stream are returned.
209          * Note this doesn't close the stream.
210          * @throws IOException if a problem occured reading the stream.
211          */
212         public static byte[] getInputStreamAsByteArray(InputStream stream, int length)
213                 throws IOException {
214                 byte[] contents;
215                 if (length == -1) {
216                         contents = new byte[0];
217                         int contentsLength = 0;
218                         int amountRead = -1;
219                         do {
220                                 int amountRequested = Math.max(stream.available(), DEFAULT_READING_SIZE);  // read at least 8K
221                                 
222                                 // resize contents if needed
223                                 if (contentsLength + amountRequested > contents.length) {
224                                         System.arraycopy(
225                                                 contents,
226                                                 0,
227                                                 contents = new byte[contentsLength + amountRequested],
228                                                 0,
229                                                 contentsLength);
230                                 }
231
232                                 // read as many bytes as possible
233                                 amountRead = stream.read(contents, contentsLength, amountRequested);
234
235                                 if (amountRead > 0) {
236                                         // remember length of contents
237                                         contentsLength += amountRead;
238                                 }
239                         } while (amountRead != -1); 
240
241                         // resize contents if necessary
242                         if (contentsLength < contents.length) {
243                                 System.arraycopy(
244                                         contents,
245                                         0,
246                                         contents = new byte[contentsLength],
247                                         0,
248                                         contentsLength);
249                         }
250                 } else {
251                         contents = new byte[length];
252                         int len = 0;
253                         int readSize = 0;
254                         while ((readSize != -1) && (len != length)) {
255                                 // See PR 1FMS89U
256                                 // We record first the read size. In this case len is the actual read size.
257                                 len += readSize;
258                                 readSize = stream.read(contents, len, length - len);
259                         }
260                 }
261
262                 return contents;
263         }
264         /*
265          * NIO support to get input stream as char array.
266          * Not used as with JDK 1.4.2 this support is slower than standard IO one...
267          * Keep it as comment for future in case of next JDK versions improve performance
268          * in this area...
269         public static char[] getInputStreamAsCharArray(FileInputStream stream, int length, String encoding)
270                 throws IOException {
271                 
272                 FileChannel channel = stream.getChannel();
273                 int size = (int)channel.size();
274                 if (length >= 0 && length < size) size = length;
275                 Charset charset = encoding==null?systemCharset:Charset.forName(encoding);
276                 if (charset != null) {
277                         MappedByteBuffer bbuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
278                     CharsetDecoder decoder = charset.newDecoder();
279                     CharBuffer buffer = decoder.decode(bbuffer);
280                     char[] contents = new char[buffer.limit()];
281                     buffer.get(contents);
282                     return contents;
283                 }
284                 throw new UnsupportedCharsetException(SYSTEM_FILE_ENCODING);
285         }
286         */
287         /**
288          * Returns the given input stream's contents as a character array.
289          * If a length is specified (ie. if length != -1), only length chars
290          * are returned. Otherwise all chars in the stream are returned.
291          * Note this doesn't close the stream.
292          * @throws IOException if a problem occured reading the stream.
293          */
294         public static char[] getInputStreamAsCharArray(InputStream stream, int length, String encoding)
295                 throws IOException {
296                 InputStreamReader reader = null;
297                 reader = encoding == null
298                                         ? new InputStreamReader(stream)
299                                         : new InputStreamReader(stream, encoding);
300                 char[] contents;
301                 if (length == -1) {
302                         contents = CharOperation.NO_CHAR;
303                         int contentsLength = 0;
304                         int amountRead = -1;
305                         do {
306                                 int amountRequested = Math.max(stream.available(), DEFAULT_READING_SIZE);  // read at least 8K
307
308                                 // resize contents if needed
309                                 if (contentsLength + amountRequested > contents.length) {
310                                         System.arraycopy(
311                                                 contents,
312                                                 0,
313                                                 contents = new char[contentsLength + amountRequested],
314                                                 0,
315                                                 contentsLength);
316                                 }
317
318                                 // read as many chars as possible
319                                 amountRead = reader.read(contents, contentsLength, amountRequested);
320
321                                 if (amountRead > 0) {
322                                         // remember length of contents
323                                         contentsLength += amountRead;
324                                 }
325                         } while (amountRead != -1);
326
327                         // Do not keep first character for UTF-8 BOM encoding
328                         int start = 0;
329                         if (contentsLength > 0 && "UTF-8".equals(encoding)) { //$NON-NLS-1$
330                                 if (contents[0] == 0xFEFF) { // if BOM char then skip
331                                         contentsLength--;
332                                         start = 1;
333                                 }
334                         }
335                         // resize contents if necessary
336                         if (contentsLength < contents.length) {
337                                 System.arraycopy(
338                                         contents,
339                                         start,
340                                         contents = new char[contentsLength],
341                                         0,
342                                         contentsLength);
343                         }
344                 } else {
345                         contents = new char[length];
346                         int len = 0;
347                         int readSize = 0;
348                         while ((readSize != -1) && (len != length)) {
349                                 // See PR 1FMS89U
350                                 // We record first the read size. In this case len is the actual read size.
351                                 len += readSize;
352                                 readSize = reader.read(contents, len, length - len);
353                         }
354                         // Do not keep first character for UTF-8 BOM encoding
355                         int start = 0;
356                         if (length > 0 && "UTF-8".equals(encoding)) { //$NON-NLS-1$
357                                 if (contents[0] == 0xFEFF) { // if BOM char then skip
358                                         len--;
359                                         start = 1;
360                                 }
361                         }
362                         // See PR 1FMS89U
363                         // Now we need to resize in case the default encoding used more than one byte for each
364                         // character
365                         if (len != length)
366                                 System.arraycopy(contents, start, (contents = new char[len]), 0, len);
367                 }
368
369                 return contents;
370         }
371         
372         /**
373          * Returns the contents of the given zip entry as a byte array.
374          * @throws IOException if a problem occured reading the zip entry.
375          */
376         public static byte[] getZipEntryByteContent(ZipEntry ze, ZipFile zip)
377                 throws IOException {
378
379                 InputStream stream = null;
380                 try {
381                         stream = new BufferedInputStream(zip.getInputStream(ze));
382                         return getInputStreamAsByteArray(stream, (int) ze.getSize());
383                 } finally {
384                         if (stream != null) {
385                                 try {
386                                         stream.close();
387                                 } catch (IOException e) {
388                                         // ignore
389                                 }
390                         }
391                 }
392         }
393         /**
394          * Returns true iff str.toLowerCase().endsWith(".jar") || str.toLowerCase().endsWith(".zip")
395          * implementation is not creating extra strings.
396          */
397         public final static boolean isArchiveFileName(String name) {
398                 int nameLength = name == null ? 0 : name.length();
399                 int suffixLength = SUFFIX_JAR.length;
400                 if (nameLength < suffixLength) return false;
401
402                 // try to match as JAR file
403                 for (int i = 0; i < suffixLength; i++) {
404                         char c = name.charAt(nameLength - i - 1);
405                         int suffixIndex = suffixLength - i - 1;
406                         if (c != SUFFIX_jar[suffixIndex] && c != SUFFIX_JAR[suffixIndex]) {
407
408                                 // try to match as ZIP file
409                                 suffixLength = SUFFIX_ZIP.length;
410                                 if (nameLength < suffixLength) return false;
411                                 for (int j = 0; j < suffixLength; j++) {
412                                         c = name.charAt(nameLength - j - 1);
413                                         suffixIndex = suffixLength - j - 1;
414                                         if (c != SUFFIX_zip[suffixIndex] && c != SUFFIX_ZIP[suffixIndex]) return false;
415                                 }
416                                 return true;
417                         }
418                 }
419                 return true;            
420         }       
421         /**
422          * Returns true iff str.toLowerCase().endsWith(".class")
423          * implementation is not creating extra strings.
424          */
425         public final static boolean isClassFileName(char[] name) {
426                 int nameLength = name == null ? 0 : name.length;
427                 int suffixLength = SUFFIX_CLASS.length;
428                 if (nameLength < suffixLength) return false;
429
430                 for (int i = 0, offset = nameLength - suffixLength; i < suffixLength; i++) {
431                         char c = name[offset + i];
432                         if (c != SUFFIX_class[i] && c != SUFFIX_CLASS[i]) return false;
433                 }
434                 return true;            
435         }       
436         /**
437          * Returns true iff str.toLowerCase().endsWith(".class")
438          * implementation is not creating extra strings.
439          */
440         public final static boolean isClassFileName(String name) {
441                 int nameLength = name == null ? 0 : name.length();
442                 int suffixLength = SUFFIX_CLASS.length;
443                 if (nameLength < suffixLength) return false;
444
445                 for (int i = 0; i < suffixLength; i++) {
446                         char c = name.charAt(nameLength - i - 1);
447                         int suffixIndex = suffixLength - i - 1;
448                         if (c != SUFFIX_class[suffixIndex] && c != SUFFIX_CLASS[suffixIndex]) return false;
449                 }
450                 return true;            
451         }       
452         /* TODO (philippe) should consider promoting it to CharOperation
453          * Returns whether the given resource path matches one of the inclusion/exclusion
454          * patterns.
455          * NOTE: should not be asked directly using pkg root pathes
456          * @see IClasspathEntry#getInclusionPatterns
457          * @see IClasspathEntry#getExclusionPatterns
458          */
459         public final static boolean isExcluded(char[] path, char[][] inclusionPatterns, char[][] exclusionPatterns, boolean isFolderPath) {
460                 if (inclusionPatterns == null && exclusionPatterns == null) return false;
461
462                 inclusionCheck: if (inclusionPatterns != null) {
463                         for (int i = 0, length = inclusionPatterns.length; i < length; i++) {
464                                 char[] pattern = inclusionPatterns[i];
465                                 char[] folderPattern = pattern;
466                                 if (isFolderPath) {
467                                         int lastSlash = CharOperation.lastIndexOf('/', pattern);
468                                         if (lastSlash != -1 && lastSlash != pattern.length-1){ // trailing slash -> adds '**' for free (see http://ant.apache.org/manual/dirtasks.html)
469                                                 int star = CharOperation.indexOf('*', pattern, lastSlash);
470                                                 if ((star == -1
471                                                                 || star >= pattern.length-1 
472                                                                 || pattern[star+1] != '*')) {
473                                                         folderPattern = CharOperation.subarray(pattern, 0, lastSlash);
474                                                 }
475                                         }
476                                 }
477                                 if (CharOperation.pathMatch(folderPattern, path, true, '/')) {
478                                         break inclusionCheck;
479                                 }
480                         }
481                         return true; // never included
482                 }
483                 if (isFolderPath) {
484                         path = CharOperation.concat(path, new char[] {'*'}, '/');
485                 }
486                 exclusionCheck: if (exclusionPatterns != null) {
487                         for (int i = 0, length = exclusionPatterns.length; i < length; i++) {
488                                 if (CharOperation.pathMatch(exclusionPatterns[i], path, true, '/')) {
489                                         return true;
490                                 }
491                         }
492                 }
493                 return false;
494         }                       
495         /**
496          * Returns true iff str.toLowerCase().endsWith(".java")
497          * implementation is not creating extra strings.
498          */
499         public final static boolean isJavaFileName(char[] name) {
500                 int nameLength = name == null ? 0 : name.length;
501                 int suffixLength = SUFFIX_JAVA.length;
502                 if (nameLength < suffixLength) return false;
503
504                 for (int i = 0, offset = nameLength - suffixLength; i < suffixLength; i++) {
505                         char c = name[offset + i];
506                         if (c != SUFFIX_java[i] && c != SUFFIX_JAVA[i]) return false;
507                 }
508                 return true;            
509         }
510         /**
511          * Returns true iff str.toLowerCase().endsWith(".java")
512          * implementation is not creating extra strings.
513          */
514         public final static boolean isJavaFileName(String name) {
515                 int nameLength = name == null ? 0 : name.length();
516                 int suffixLength = SUFFIX_JAVA.length;
517                 if (nameLength < suffixLength) return false;
518
519                 for (int i = 0; i < suffixLength; i++) {
520                         char c = name.charAt(nameLength - i - 1);
521                         int suffixIndex = suffixLength - i - 1;
522                         if (c != SUFFIX_java[suffixIndex] && c != SUFFIX_JAVA[suffixIndex]) return false;
523                 }
524                 return true;            
525         }
526         /**
527          * Creates a NLS catalog for the given locale.
528          */
529         public static void relocalize() {
530                 try {
531                         bundle = ResourceBundle.getBundle(bundleName, Locale.getDefault());
532                 } catch(MissingResourceException e) {
533                         System.out.println("Missing resource : " + bundleName.replace('.', '/') + ".properties for locale " + Locale.getDefault()); //$NON-NLS-1$//$NON-NLS-2$
534                         throw e;
535                 }
536         }
537
538         /**
539          * Converts a boolean value into Boolean.
540          * @param bool The boolean to convert
541          * @return The corresponding Boolean object (TRUE or FALSE).
542          */
543         public static Boolean toBoolean(boolean bool) {
544                 if (bool) {
545                         return Boolean.TRUE;
546                 } else {
547                         return Boolean.FALSE;
548                 }
549         }
550         /**
551          * Converts an array of Objects into String.
552          */
553         public static String toString(Object[] objects) {
554                 return toString(objects, 
555                         new Displayable(){ 
556                                 public String displayString(Object o) { 
557                                         if (o == null) return "null"; //$NON-NLS-1$
558                                         return o.toString(); 
559                                 }
560                         });
561         }
562
563         /**
564          * Converts an array of Objects into String.
565          */
566         public static String toString(Object[] objects, Displayable renderer) {
567                 if (objects == null) return ""; //$NON-NLS-1$
568                 StringBuffer buffer = new StringBuffer(10);
569                 for (int i = 0; i < objects.length; i++){
570                         if (i > 0) buffer.append(", "); //$NON-NLS-1$
571                         buffer.append(renderer.displayString(objects[i]));
572                 }
573                 return buffer.toString();
574         }
575 }