File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\Util.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson;
25
26 import com.sun.jna.Memory;
27 import com.sun.jna.Native;
28 import com.sun.jna.NativeLong;
29 import edu.umd.cs.findbugs.annotations.SuppressWarnings;
30 import hudson.Proc.LocalProc;
31 import hudson.model.TaskListener;
32 import hudson.os.PosixAPI;
33 import hudson.util.IOException2;
34 import hudson.util.QuotedStringTokenizer;
35 import hudson.util.VariableResolver;
36 import jenkins.model.Jenkins;
37 import org.apache.commons.io.IOUtils;
38 import org.apache.commons.lang.time.FastDateFormat;
39 import org.apache.tools.ant.BuildException;
40 import org.apache.tools.ant.Project;
41 import org.apache.tools.ant.taskdefs.Chmod;
42 import org.apache.tools.ant.taskdefs.Copy;
43 import org.apache.tools.ant.types.FileSet;
44 import org.jruby.ext.posix.FileStat;
45 import org.jruby.ext.posix.POSIX;
46 import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
47 import org.kohsuke.stapler.Stapler;
48
49 import javax.crypto.SecretKey;
50 import javax.crypto.spec.SecretKeySpec;
51 import java.io.*;
52 import java.lang.reflect.Array;
53 import java.lang.reflect.InvocationTargetException;
54 import java.net.InetAddress;
55 import java.net.URI;
56 import java.net.URISyntaxException;
57 import java.net.UnknownHostException;
58 import java.nio.ByteBuffer;
59 import java.nio.CharBuffer;
60 import java.nio.charset.CharacterCodingException;
61 import java.nio.charset.Charset;
62 import java.nio.charset.CharsetEncoder;
63 import java.security.DigestInputStream;
64 import java.security.MessageDigest;
65 import java.security.NoSuchAlgorithmException;
66 import java.text.NumberFormat;
67 import java.text.ParseException;
68 import java.util.*;
69 import java.util.concurrent.atomic.AtomicBoolean;
70 import java.util.logging.Level;
71 import java.util.logging.Logger;
72 import java.util.regex.Matcher;
73 import java.util.regex.Pattern;
74
75 import hudson.util.jna.Kernel32Utils;
76
77 import static hudson.util.jna.GNUCLibrary.LIBC;
78
79 /**
80 * Various utility methods that don't have more proper home.
81 *
82 * @author Kohsuke Kawaguchi
83 */
84 public class Util {
85
86 // Constant number of milliseconds in various time units.
87 private static final long ONE_SECOND_MS = 1000;
88 private static final long ONE_MINUTE_MS = 60 * ONE_SECOND_MS;
89 private static final long ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
90 private static final long ONE_DAY_MS = 24 * ONE_HOUR_MS;
91 private static final long ONE_MONTH_MS = 30 * ONE_DAY_MS;
92 private static final long ONE_YEAR_MS = 365 * ONE_DAY_MS;
93
94 /**
95 * Creates a filtered sublist.
96 * @since 1.176
97 */
98 public static <T> List<T> filter( Iterable<?> base, Class<T> type ) {
99 List<T> r = new ArrayList<T>();
100 for (Object i : base) {
101 if(type.isInstance(i))
102 r.add(type.cast(i));
103 }
104 return r;
105 }
106
107 /**
108 * Creates a filtered sublist.
109 */
110 public static <T> List<T> filter( List<?> base, Class<T> type ) {
111 return filter((Iterable)base,type);
112 }
113
114 /**
115 * Pattern for capturing variables. Either $xyz or ${xyz}, while ignoring "$$"
116 */
117 private static final Pattern VARIABLE = Pattern.compile("\\$([A-Za-z0-9_]+|\\{[A-Za-z0-9_]+\\}|\\$)");
118
119 /**
120 * Replaces the occurrence of '$key' by <tt>properties.get('key')</tt>.
121 *
122 * <p>
123 * Unlike shell, undefined variables are left as-is (this behavior is the same as Ant.)
124 *
125 */
126 public static String replaceMacro(String s, Map<String,String> properties) {
127 return replaceMacro(s,new VariableResolver.ByMap<String>(properties));
128 }
129
130 /**
131 * Replaces the occurrence of '$key' by <tt>resolver.get('key')</tt>.
132 *
133 * <p>
134 * Unlike shell, undefined variables are left as-is (this behavior is the same as Ant.)
135 */
136 public static String replaceMacro(String s, VariableResolver<String> resolver) {
137 if (s == null) {
138 return null;
139 }
140
141 int idx=0;
142 while(true) {
143 Matcher m = VARIABLE.matcher(s);
144 if(!m.find(idx)) return s;
145
146 String key = m.group().substring(1);
147
148 // escape the dollar sign or get the key to resolve
149 String value;
150 if(key.charAt(0)=='$') {
151 value = "$";
152 } else {
153 if(key.charAt(0)=='{') key = key.substring(1,key.length()-1);
154 value = resolver.resolve(key);
155 }
156
157 if(value==null)
158 idx = m.end(); // skip this
159 else {
160 s = s.substring(0,m.start())+value+s.substring(m.end());
161 idx = m.start() + value.length();
162 }
163 }
164 }
165
166 /**
167 * Loads the contents of a file into a string.
168 */
169 public static String loadFile(File logfile) throws IOException {
170 return loadFile(logfile, Charset.defaultCharset());
171 }
172
173 public static String loadFile(File logfile,Charset charset) throws IOException {
174 if(!logfile.exists())
175 return "";
176
177 StringBuilder str = new StringBuilder((int)logfile.length());
178
179 BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(logfile),charset));
180 try {
181 char[] buf = new char[1024];
182 int len;
183 while((len=r.read(buf,0,buf.length))>0)
184 str.append(buf,0,len);
185 } finally {
186 r.close();
187 }
188
189 return str.toString();
190 }
191
192 /**
193 * Deletes the contents of the given directory (but not the directory itself)
194 * recursively.
195 *
196 * @throws IOException
197 * if the operation fails.
198 */
199 public static void deleteContentsRecursive(File file) throws IOException {
200 File[] files = file.listFiles();
201 if(files==null)
202 return; // the directory didn't exist in the first place
203 for (File child : files)
204 deleteRecursive(child);
205 }
206
207 /**
208 * Deletes this file (and does not take no for an answer).
209 * @param f a file to delete
210 * @throws IOException if it exists but could not be successfully deleted
211 */
212 public static void deleteFile(File f) throws IOException {
213 if (!f.delete()) {
214 if(!f.exists())
215 // we are trying to delete a file that no longer exists, so this is not an error
216 return;
217
218 // perhaps this file is read-only?
219 makeWritable(f);
220 /*
221 on Unix both the file and the directory that contains it has to be writable
222 for a file deletion to be successful. (Confirmed on Solaris 9)
223
224 $ ls -la
225 total 6
226 dr-xr-sr-x 2 hudson hudson 512 Apr 18 14:41 .
227 dr-xr-sr-x 3 hudson hudson 512 Apr 17 19:36 ..
228 -r--r--r-- 1 hudson hudson 469 Apr 17 19:36 manager.xml
229 -rw-r--r-- 1 hudson hudson 0 Apr 18 14:41 x
230 $ rm x
231 rm: x not removed: Permission denied
232 */
233
234 makeWritable(f.getParentFile());
235
236 if(!f.delete() && f.exists()) {
237 // trouble-shooting.
238 // see http://www.nabble.com/Sometimes-can%27t-delete-files-from-hudson.scm.SubversionSCM%24CheckOutTask.invoke%28%29-tt17333292.html
239 // I suspect other processes putting files in this directory
240 File[] files = f.listFiles();
241 if(files!=null && files.length>0)
242 throw new IOException("Unable to delete " + f.getPath()+" - files in dir: "+Arrays.asList(files));
243 throw new IOException("Unable to delete " + f.getPath());
244 }
245 }
246 }
247
248 /**
249 * Makes the given file writable by any means possible.
250 */
251 @IgnoreJRERequirement
252 private static void makeWritable(File f) {
253 // try chmod. this becomes no-op if this is not Unix.
254 try {
255 Chmod chmod = new Chmod();
256 chmod.setProject(new Project());
257 chmod.setFile(f);
258 chmod.setPerm("u+w");
259 chmod.execute();
260 } catch (BuildException e) {
261 LOGGER.log(Level.INFO,"Failed to chmod "+f,e);
262 }
263
264 // also try JDK6-way of doing it.
265 try {
266 f.setWritable(true);
267 } catch (NoSuchMethodError e) {
268 // not JDK6
269 }
270
271 try {// try libc chmod
272 POSIX posix = PosixAPI.get();
273 String path = f.getAbsolutePath();
274 FileStat stat = posix.stat(path);
275 posix.chmod(path, stat.mode()|0200); // u+w
276 } catch (Throwable t) {
277 LOGGER.log(Level.FINE,"Failed to chmod(2) "+f,t);
278 }
279
280 }
281
282 public static void deleteRecursive(File dir) throws IOException {
283 if(!isSymlink(dir))
284 deleteContentsRecursive(dir);
285 try {
286 deleteFile(dir);
287 } catch (IOException e) {
288 // if some of the child directories are big, it might take long enough to delete that
289 // it allows others to create new files, causing problemsl ike JENKINS-10113
290 // so give it one more attempt before we give up.
291 if(!isSymlink(dir))
292 deleteContentsRecursive(dir);
293 deleteFile(dir);
294 }
295 }
296
297 /*
298 * Copyright 2001-2004 The Apache Software Foundation.
299 *
300 * Licensed under the Apache License, Version 2.0 (the "License");
301 * you may not use this file except in compliance with the License.
302 * You may obtain a copy of the License at
303 *
304 * http://www.apache.org/licenses/LICENSE-2.0
305 *
306 * Unless required by applicable law or agreed to in writing, software
307 * distributed under the License is distributed on an "AS IS" BASIS,
308 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
309 * See the License for the specific language governing permissions and
310 * limitations under the License.
311 */
312 /**
313 * Checks if the given file represents a symlink.
314 */
315 //Taken from http://svn.apache.org/viewvc/maven/shared/trunk/file-management/src/main/java/org/apache/maven/shared/model/fileset/util/FileSetManager.java?view=markup
316 public static boolean isSymlink(File file) throws IOException {
317 Boolean r = isSymlinkJava7(file);
318 if (r != null) {
319 return r;
320 }
321 if (Functions.isWindows()) {
322 return Kernel32Utils.isJunctionOrSymlink(file);
323 }
324 String name = file.getName();
325 if (name.equals(".") || name.equals(".."))
326 return false;
327
328 File fileInCanonicalParent;
329 File parentDir = file.getParentFile();
330 if ( parentDir == null ) {
331 fileInCanonicalParent = file;
332 } else {
333 fileInCanonicalParent = new File( parentDir.getCanonicalPath(), name );
334 }
335 return !fileInCanonicalParent.getCanonicalFile().equals( fileInCanonicalParent.getAbsoluteFile() );
336 }
337
338 @SuppressWarnings("NP_BOOLEAN_RETURN_NULL")
339 private static Boolean isSymlinkJava7(File file) throws IOException {
340 try {
341 Object path = File.class.getMethod("toPath").invoke(file);
342 return (Boolean) Class.forName("java.nio.file.Files").getMethod("isSymbolicLink", Class.forName("java.nio.file.Path")).invoke(null, path);
343 } catch (NoSuchMethodException x) {
344 return null; // fine, Java 5/6
345 } catch (Exception x) {
346 throw (IOException) new IOException(x.toString()).initCause(x);
347 }
348 }
349
350 /**
351 * Creates a new temporary directory.
352 */
353 public static File createTempDir() throws IOException {
354 File tmp = File.createTempFile("hudson", "tmp");
355 if(!tmp.delete())
356 throw new IOException("Failed to delete "+tmp);
357 if(!tmp.mkdirs())
358 throw new IOException("Failed to create a new directory "+tmp);
359 return tmp;
360 }
361
362 private static final Pattern errorCodeParser = Pattern.compile(".*CreateProcess.*error=([0-9]+).*");
363
364 /**
365 * On Windows, error messages for IOException aren't very helpful.
366 * This method generates additional user-friendly error message to the listener
367 */
368 public static void displayIOException( IOException e, TaskListener listener ) {
369 String msg = getWin32ErrorMessage(e);
370 if(msg!=null)
371 listener.getLogger().println(msg);
372 }
373
374 public static String getWin32ErrorMessage(IOException e) {
375 return getWin32ErrorMessage((Throwable)e);
376 }
377
378 /**
379 * Extracts the Win32 error message from {@link Throwable} if possible.
380 *
381 * @return
382 * null if there seems to be no error code or if the platform is not Win32.
383 */
384 public static String getWin32ErrorMessage(Throwable e) {
385 String msg = e.getMessage();
386 if(msg!=null) {
387 Matcher m = errorCodeParser.matcher(msg);
388 if(m.matches()) {
389 try {
390 ResourceBundle rb = ResourceBundle.getBundle("/hudson/win32errors");
391 return rb.getString("error"+m.group(1));
392 } catch (Exception _) {
393 // silently recover from resource related failures
394 }
395 }
396 }
397
398 if(e.getCause()!=null)
399 return getWin32ErrorMessage(e.getCause());
400 return null; // no message
401 }
402
403 /**
404 * Gets a human readable message for the given Win32 error code.
405 *
406 * @return
407 * null if no such message is available.
408 */
409 public static String getWin32ErrorMessage(int n) {
410 try {
411 ResourceBundle rb = ResourceBundle.getBundle("/hudson/win32errors");
412 return rb.getString("error"+n);
413 } catch (MissingResourceException e) {
414 LOGGER.log(Level.WARNING,"Failed to find resource bundle",e);
415 return null;
416 }
417 }
418
419 /**
420 * Guesses the current host name.
421 */
422 public static String getHostName() {
423 try {
424 return InetAddress.getLocalHost().getHostName();
425 } catch (UnknownHostException e) {
426 return "localhost";
427 }
428 }
429
430 public static void copyStream(InputStream in,OutputStream out) throws IOException {
431 byte[] buf = new byte[8192];
432 int len;
433 while((len=in.read(buf))>0)
434 out.write(buf,0,len);
435 }
436
437 public static void copyStream(Reader in, Writer out) throws IOException {
438 char[] buf = new char[8192];
439 int len;
440 while((len=in.read(buf))>0)
441 out.write(buf,0,len);
442 }
443
444 public static void copyStreamAndClose(InputStream in,OutputStream out) throws IOException {
445 try {
446 copyStream(in,out);
447 } finally {
448 IOUtils.closeQuietly(in);
449 IOUtils.closeQuietly(out);
450 }
451 }
452
453 public static void copyStreamAndClose(Reader in,Writer out) throws IOException {
454 try {
455 copyStream(in,out);
456 } finally {
457 IOUtils.closeQuietly(in);
458 IOUtils.closeQuietly(out);
459 }
460 }
461
462 /**
463 * Tokenizes the text separated by delimiters.
464 *
465 * <p>
466 * In 1.210, this method was changed to handle quotes like Unix shell does.
467 * Before that, this method just used {@link StringTokenizer}.
468 *
469 * @since 1.145
470 * @see QuotedStringTokenizer
471 */
472 public static String[] tokenize(String s,String delimiter) {
473 return QuotedStringTokenizer.tokenize(s,delimiter);
474 }
475
476 public static String[] tokenize(String s) {
477 return tokenize(s," \t\n\r\f");
478 }
479
480 /**
481 * Converts the map format of the environment variables to the K=V format in the array.
482 */
483 public static String[] mapToEnv(Map<String,String> m) {
484 String[] r = new String[m.size()];
485 int idx=0;
486
487 for (final Map.Entry<String,String> e : m.entrySet()) {
488 r[idx++] = e.getKey() + '=' + e.getValue();
489 }
490 return r;
491 }
492
493 public static int min(int x, int... values) {
494 for (int i : values) {
495 if(i<x)
496 x=i;
497 }
498 return x;
499 }
500
501 public static String nullify(String v) {
502 return fixEmpty(v);
503 }
504
505 public static String removeTrailingSlash(String s) {
506 if(s.endsWith("/")) return s.substring(0,s.length()-1);
507 else return s;
508 }
509
510 /**
511 * Computes MD5 digest of the given input stream.
512 *
513 * @param source
514 * The stream will be closed by this method at the end of this method.
515 * @return
516 * 32-char wide string
517 */
518 public static String getDigestOf(InputStream source) throws IOException {
519 try {
520 MessageDigest md5 = MessageDigest.getInstance("MD5");
521
522 byte[] buffer = new byte[1024];
523 DigestInputStream in =new DigestInputStream(source,md5);
524 try {
525 while(in.read(buffer)>0)
526 ; // simply discard the input
527 } finally {
528 in.close();
529 }
530 return toHexString(md5.digest());
531 } catch (NoSuchAlgorithmException e) {
532 throw new IOException2("MD5 not installed",e); // impossible
533 }
534 }
535
536 public static String getDigestOf(String text) {
537 try {
538 return getDigestOf(new ByteArrayInputStream(text.getBytes("UTF-8")));
539 } catch (IOException e) {
540 throw new Error(e);
541 }
542 }
543
544 /**
545 * Converts a string into 128-bit AES key.
546 * @since 1.308
547 */
548 public static SecretKey toAes128Key(String s) {
549 try {
550 // turn secretKey into 256 bit hash
551 MessageDigest digest = MessageDigest.getInstance("SHA-256");
552 digest.reset();
553 digest.update(s.getBytes("UTF-8"));
554
555 // Due to the stupid US export restriction JDK only ships 128bit version.
556 return new SecretKeySpec(digest.digest(),0,128/8, "AES");
557 } catch (NoSuchAlgorithmException e) {
558 throw new Error(e);
559 } catch (UnsupportedEncodingException e) {
560 throw new Error(e);
561 }
562 }
563
564 public static String toHexString(byte[] data, int start, int len) {
565 StringBuilder buf = new StringBuilder();
566 for( int i=0; i<len; i++ ) {
567 int b = data[start+i]&0xFF;
568 if(b<16) buf.append('0');
569 buf.append(Integer.toHexString(b));
570 }
571 return buf.toString();
572 }
573
574 public static String toHexString(byte[] bytes) {
575 return toHexString(bytes,0,bytes.length);
576 }
577
578 public static byte[] fromHexString(String data) {
579 byte[] r = new byte[data.length() / 2];
580 for (int i = 0; i < data.length(); i += 2)
581 r[i / 2] = (byte) Integer.parseInt(data.substring(i, i + 2), 16);
582 return r;
583 }
584
585 /**
586 * Returns a human readable text of the time duration, for example "3 minutes 40 seconds".
587 * This version should be used for representing a duration of some activity (like build)
588 *
589 * @param duration
590 * number of milliseconds.
591 */
592 public static String getTimeSpanString(long duration) {
593 // Break the duration up in to units.
594 long years = duration / ONE_YEAR_MS;
595 duration %= ONE_YEAR_MS;
596 long months = duration / ONE_MONTH_MS;
597 duration %= ONE_MONTH_MS;
598 long days = duration / ONE_DAY_MS;
599 duration %= ONE_DAY_MS;
600 long hours = duration / ONE_HOUR_MS;
601 duration %= ONE_HOUR_MS;
602 long minutes = duration / ONE_MINUTE_MS;
603 duration %= ONE_MINUTE_MS;
604 long seconds = duration / ONE_SECOND_MS;
605 duration %= ONE_SECOND_MS;
606 long millisecs = duration;
607
608 if (years > 0)
609 return makeTimeSpanString(years, Messages.Util_year(years), months, Messages.Util_month(months));
610 else if (months > 0)
611 return makeTimeSpanString(months, Messages.Util_month(months), days, Messages.Util_day(days));
612 else if (days > 0)
613 return makeTimeSpanString(days, Messages.Util_day(days), hours, Messages.Util_hour(hours));
614 else if (hours > 0)
615 return makeTimeSpanString(hours, Messages.Util_hour(hours), minutes, Messages.Util_minute(minutes));
616 else if (minutes > 0)
617 return makeTimeSpanString(minutes, Messages.Util_minute(minutes), seconds, Messages.Util_second(seconds));
618 else if (seconds >= 10)
619 return Messages.Util_second(seconds);
620 else if (seconds >= 1)
621 return Messages.Util_second(seconds+(float)(millisecs/100)/10); // render "1.2 sec"
622 else if(millisecs>=100)
623 return Messages.Util_second((float)(millisecs/10)/100); // render "0.12 sec".
624 else
625 return Messages.Util_millisecond(millisecs);
626 }
627
628
629 /**
630 * Create a string representation of a time duration. If the quantity of
631 * the most significant unit is big (>=10), then we use only that most
632 * significant unit in the string representation. If the quantity of the
633 * most significant unit is small (a single-digit value), then we also
634 * use a secondary, smaller unit for increased precision.
635 * So 13 minutes and 43 seconds returns just "13 minutes", but 3 minutes
636 * and 43 seconds is "3 minutes 43 seconds".
637 */
638 private static String makeTimeSpanString(long bigUnit,
639 String bigLabel,
640 long smallUnit,
641 String smallLabel) {
642 String text = bigLabel;
643 if (bigUnit < 10)
644 text += ' ' + smallLabel;
645 return text;
646 }
647
648
649 /**
650 * Get a human readable string representing strings like "xxx days ago",
651 * which should be used to point to the occurrence of an event in the past.
652 */
653 public static String getPastTimeString(long duration) {
654 return Messages.Util_pastTime(getTimeSpanString(duration));
655 }
656
657
658 /**
659 * Combines number and unit, with a plural suffix if needed.
660 *
661 * @deprecated
662 * Use individual localization methods instead.
663 * See {@link Messages#Util_year(Object)} for an example.
664 * Deprecated since 2009-06-24, remove method after 2009-12-24.
665 */
666 public static String combine(long n, String suffix) {
667 String s = Long.toString(n)+' '+suffix;
668 if(n!=1)
669 // Just adding an 's' won't work in most natural languages, even English has exception to the rule (e.g. copy/copies).
670 s += "s";
671 return s;
672 }
673
674 /**
675 * Create a sub-list by only picking up instances of the specified type.
676 */
677 public static <T> List<T> createSubList( Collection<?> source, Class<T> type ) {
678 List<T> r = new ArrayList<T>();
679 for (Object item : source) {
680 if(type.isInstance(item))
681 r.add(type.cast(item));
682 }
683 return r;
684 }
685
686 /**
687 * Escapes non-ASCII characters in URL.
688 *
689 * <p>
690 * Note that this methods only escapes non-ASCII but leaves other URL-unsafe characters,
691 * such as '#'.
692 * {@link #rawEncode(String)} should generally be used instead, though be careful to pass only
693 * a single path component to that method (it will encode /, but this method does not).
694 */
695 public static String encode(String s) {
696 try {
697 boolean escaped = false;
698
699 StringBuilder out = new StringBuilder(s.length());
700
701 ByteArrayOutputStream buf = new ByteArrayOutputStream();
702 OutputStreamWriter w = new OutputStreamWriter(buf,"UTF-8");
703
704 for (int i = 0; i < s.length(); i++) {
705 int c = s.charAt(i);
706 if (c<128 && (c!=' ' && c!='&')) {
707 out.append((char) c);
708 } else {
709 // 1 char -> UTF8
710 w.write(c);
711 w.flush();
712 for (byte b : buf.toByteArray()) {
713 out.append('%');
714 out.append(toDigit((b >> 4) & 0xF));
715 out.append(toDigit(b & 0xF));
716 }
717 buf.reset();
718 escaped = true;
719 }
720 }
721
722 return escaped ? out.toString() : s;
723 } catch (IOException e) {
724 throw new Error(e); // impossible
725 }
726 }
727
728 private static final boolean[] uriMap = new boolean[123];
729 static {
730 String raw =
731 "! $ '()*+,-. 0123456789 = @ABCDEFGHIJKLMNOPQRSTUVWXYZ _ abcdefghijklmnopqrstuvwxyz";
732 // "# %& / :;< >? [\]^ ` {|}~
733 // ^--so these are encoded
734 int i;
735 // Encode control chars and space
736 for (i = 0; i < 33; i++) uriMap[i] = true;
737 for (int j = 0; j < raw.length(); i++, j++)
738 uriMap[i] = (raw.charAt(j) == ' ');
739 // If we add encodeQuery() just add a 2nd map to encode &+=
740 // queryMap[38] = queryMap[43] = queryMap[61] = true;
741 }
742
743 /**
744 * Encode a single path component for use in an HTTP URL.
745 * Escapes all non-ASCII, general unsafe (space and "#%<>[\]^`{|}~)
746 * and HTTP special characters (/;:?) as specified in RFC1738.
747 * Also escapes & for convenience.
748 * (so alphanumeric and !@$*()-_=+',. are not encoded)
749 * Note that slash(/) is encoded, so the given string should be a
750 * single path component used in constructing a URL.
751 * Do not use this method for encoding query parameters.
752 * Method name inspired by PHP's rawurlencode.
753 */
754 public static String rawEncode(String s) {
755 boolean escaped = false;
756 StringBuilder out = null;
757 CharsetEncoder enc = null;
758 CharBuffer buf = null;
759 char c;
760 for (int i = 0, m = s.length(); i < m; i++) {
761 c = s.charAt(i);
762 if (c > 122 || uriMap[c]) {
763 if (!escaped) {
764 out = new StringBuilder(i + (m - i) * 3);
765 out.append(s.substring(0, i));
766 enc = Charset.forName("UTF-8").newEncoder();
767 buf = CharBuffer.allocate(1);
768 escaped = true;
769 }
770 // 1 char -> UTF8
771 buf.put(0,c);
772 buf.rewind();
773 try {
774 ByteBuffer bytes = enc.encode(buf);
775 while (bytes.hasRemaining()) {
776 byte b = bytes.get();
777 out.append('%');
778 out.append(toDigit((b >> 4) & 0xF));
779 out.append(toDigit(b & 0xF));
780 }
781 } catch (CharacterCodingException ex) { }
782 } else if (escaped) {
783 out.append(c);
784 }
785 }
786 return escaped ? out.toString() : s;
787 }
788
789 private static char toDigit(int n) {
790 return (char)(n < 10 ? '0' + n : 'A' + n - 10);
791 }
792
793 /**
794 * Surrounds by a single-quote.
795 */
796 public static String singleQuote(String s) {
797 return '\''+s+'\'';
798 }
799
800 /**
801 * Escapes HTML unsafe characters like &lt;, &amp; to the respective character entities.
802 */
803 public static String escape(String text) {
804 if (text==null) return null;
805 StringBuilder buf = new StringBuilder(text.length()+64);
806 for( int i=0; i<text.length(); i++ ) {
807 char ch = text.charAt(i);
808 if(ch=='\n')
809 buf.append("<br>");
810 else
811 if(ch=='<')
812 buf.append("&lt;");
813 else
814 if(ch=='&')
815 buf.append("&amp;");
816 else
817 if(ch=='"')
818 buf.append("&quot;");
819 else
820 if(ch=='\'')
821 buf.append("&#039;");
822 else
823 if(ch==' ') {
824 // All spaces in a block of consecutive spaces are converted to
825 // non-breaking space (&nbsp;) except for the last one. This allows
826 // significant whitespace to be retained without prohibiting wrapping.
827 char nextCh = i+1 < text.length() ? text.charAt(i+1) : 0;
828 buf.append(nextCh==' ' ? "&nbsp;" : " ");
829 }
830 else
831 buf.append(ch);
832 }
833 return buf.toString();
834 }
835
836 public static String xmlEscape(String text) {
837 StringBuilder buf = new StringBuilder(text.length()+64);
838 for( int i=0; i<text.length(); i++ ) {
839 char ch = text.charAt(i);
840 if(ch=='<')
841 buf.append("&lt;");
842 else
843 if(ch=='&')
844 buf.append("&amp;");
845 else
846 buf.append(ch);
847 }
848 return buf.toString();
849 }
850
851 /**
852 * Creates an empty file.
853 */
854 public static void touch(File file) throws IOException {
855 new FileOutputStream(file).close();
856 }
857
858 /**
859 * Copies a single file by using Ant.
860 */
861 public static void copyFile(File src, File dst) throws BuildException {
862 Copy cp = new Copy();
863 cp.setProject(new org.apache.tools.ant.Project());
864 cp.setTofile(dst);
865 cp.setFile(src);
866 cp.setOverwrite(true);
867 cp.execute();
868 }
869
870 /**
871 * Convert null to "".
872 */
873 public static String fixNull(String s) {
874 if(s==null) return "";
875 else return s;
876 }
877
878 /**
879 * Convert empty string to null.
880 */
881 public static String fixEmpty(String s) {
882 if(s==null || s.length()==0) return null;
883 return s;
884 }
885
886 /**
887 * Convert empty string to null, and trim whitespace.
888 *
889 * @since 1.154
890 */
891 public static String fixEmptyAndTrim(String s) {
892 if(s==null) return null;
893 return fixEmpty(s.trim());
894 }
895
896 public static <T> List<T> fixNull(List<T> l) {
897 return l!=null ? l : Collections.<T>emptyList();
898 }
899
900 public static <T> Set<T> fixNull(Set<T> l) {
901 return l!=null ? l : Collections.<T>emptySet();
902 }
903
904 public static <T> Collection<T> fixNull(Collection<T> l) {
905 return l!=null ? l : Collections.<T>emptySet();
906 }
907
908 public static <T> Iterable<T> fixNull(Iterable<T> l) {
909 return l!=null ? l : Collections.<T>emptySet();
910 }
911
912 /**
913 * Cuts all the leading path portion and get just the file name.
914 */
915 public static String getFileName(String filePath) {
916 int idx = filePath.lastIndexOf('\\');
917 if(idx>=0)
918 return getFileName(filePath.substring(idx+1));
919 idx = filePath.lastIndexOf('/');
920 if(idx>=0)
921 return getFileName(filePath.substring(idx+1));
922 return filePath;
923 }
924
925 /**
926 * Concatenate multiple strings by inserting a separator.
927 */
928 public static String join(Collection<?> strings, String separator) {
929 StringBuilder buf = new StringBuilder();
930 boolean first=true;
931 for (Object s : strings) {
932 if(first) first=false;
933 else buf.append(separator);
934 buf.append(s);
935 }
936 return buf.toString();
937 }
938
939 /**
940 * Combines all the given collections into a single list.
941 */
942 public static <T> List<T> join(Collection<? extends T>... items) {
943 int size = 0;
944 for (Collection<? extends T> item : items)
945 size += item.size();
946 List<T> r = new ArrayList<T>(size);
947 for (Collection<? extends T> item : items)
948 r.addAll(item);
949 return r;
950 }
951
952 /**
953 * Creates Ant {@link FileSet} with the base dir and include pattern.
954 *
955 * <p>
956 * The difference with this and using {@link FileSet#setIncludes(String)}
957 * is that this method doesn't treat whitespace as a pattern separator,
958 * which makes it impossible to use space in the file path.
959 *
960 * @param includes
961 * String like "foo/bar/*.xml" Multiple patterns can be separated
962 * by ',', and whitespace can surround ',' (so that you can write
963 * "abc, def" and "abc,def" to mean the same thing.
964 * @param excludes
965 * Exclusion pattern. Follows the same format as the 'includes' parameter.
966 * Can be null.
967 * @since 1.172
968 */
969 public static FileSet createFileSet(File baseDir, String includes, String excludes) {
970 FileSet fs = new FileSet();
971 fs.setDir(baseDir);
972 fs.setProject(new Project());
973
974 StringTokenizer tokens;
975
976 tokens = new StringTokenizer(includes,",");
977 while(tokens.hasMoreTokens()) {
978 String token = tokens.nextToken().trim();
979 fs.createInclude().setName(token);
980 }
981 if(excludes!=null) {
982 tokens = new StringTokenizer(excludes,",");
983 while(tokens.hasMoreTokens()) {
984 String token = tokens.nextToken().trim();
985 fs.createExclude().setName(token);
986 }
987 }
988 return fs;
989 }
990
991 public static FileSet createFileSet(File baseDir, String includes) {
992 return createFileSet(baseDir,includes,null);
993 }
994
995 /**
996 * Creates a symlink to baseDir+targetPath at baseDir+symlinkPath.
997 * <p>
998 * If there's a prior symlink at baseDir+symlinkPath, it will be overwritten.
999 *
1000 * @param baseDir
1001 * Base directory to resolve the 'symlinkPath' parameter.
1002 * @param targetPath
1003 * The file that the symlink should point to.
1004 * @param symlinkPath
1005 * Where to create a symlink in.
1006 */
1007 public static void createSymlink(File baseDir, String targetPath, String symlinkPath, TaskListener listener) throws InterruptedException {
1008 try {
1009 if (createSymlinkJava7(baseDir, targetPath, symlinkPath)) {
1010 return;
1011 }
1012 if (Functions.isWindows() || NO_SYMLINK) {
1013 return;
1014 }
1015 String errmsg = "";
1016 // if a file or a directory exists here, delete it first.
1017 // try simple delete first (whether exists() or not, as it may be symlink pointing
1018 // to non-existent target), but fallback to "rm -rf" to delete non-empty dir.
1019 File symlinkFile = new File(baseDir, symlinkPath);
1020 if (!symlinkFile.delete() && symlinkFile.exists())
1021 // ignore a failure.
1022 new LocalProc(new String[]{"rm","-rf", symlinkPath},new String[0],listener.getLogger(), baseDir).join();
1023
1024 Integer r=null;
1025 if (!SYMLINK_ESCAPEHATCH) {
1026 try {
1027 r = LIBC.symlink(targetPath,symlinkFile.getAbsolutePath());
1028 if (r!=0) {
1029 r = Native.getLastError();
1030 errmsg = LIBC.strerror(r);
1031 }
1032 } catch (LinkageError e) {
1033 // if JNA is unavailable, fall back.
1034 // we still prefer to try JNA first as PosixAPI supports even smaller platforms.
1035 if (PosixAPI.supportsNative()) {
1036 r = PosixAPI.get().symlink(targetPath,symlinkFile.getAbsolutePath());
1037 }
1038 }
1039 }
1040 if (r==null) {
1041 // if all else fail, fall back to the most expensive approach of forking a process
1042 r = new LocalProc(new String[]{
1043 "ln","-s", targetPath, symlinkPath},
1044 new String[0],listener.getLogger(), baseDir).join();
1045 }
1046 if (r!=0)
1047 listener.getLogger().println(String.format("ln -s %s %s failed: %d %s",targetPath, symlinkFile, r, errmsg));
1048 } catch (IOException e) {
1049 PrintStream log = listener.getLogger();
1050 log.printf("ln %s %s failed%n",targetPath, new File(baseDir, symlinkPath));
1051 Util.displayIOException(e,listener);
1052 e.printStackTrace( log );
1053 }
1054 }
1055
1056 private static final AtomicBoolean warnedSymlinks = new AtomicBoolean();
1057 private static boolean createSymlinkJava7(File baseDir, String targetPath, String symlinkPath) throws IOException {
1058 try {
1059 Object path = File.class.getMethod("toPath").invoke(new File(baseDir, symlinkPath));
1060 Object target = Class.forName("java.nio.file.Paths").getMethod("get", String.class, String[].class).invoke(null, targetPath, new String[0]);
1061 Class<?> filesC = Class.forName("java.nio.file.Files");
1062 Class<?> pathC = Class.forName("java.nio.file.Path");
1063 filesC.getMethod("deleteIfExists", pathC).invoke(null, path);
1064 Object noAttrs = Array.newInstance(Class.forName("java.nio.file.attribute.FileAttribute"), 0);
1065 filesC.getMethod("createSymbolicLink", pathC, pathC, noAttrs.getClass()).invoke(null, path, target, noAttrs);
1066 return true;
1067 } catch (NoSuchMethodException x) {
1068 return false; // fine, Java 5/6
1069 } catch (InvocationTargetException x) {
1070 Throwable x2 = x.getCause();
1071 if (x2 instanceof UnsupportedOperationException) {
1072 return true; // no symlinks on this platform
1073 }
1074 if (Functions.isWindows() && String.valueOf(x2).contains("java.nio.file.FileSystemException")) {
1075 if (warnedSymlinks.compareAndSet(false, true)) {
1076 LOGGER.warning("Symbolic links enabled on this platform but disabled for this user; run as administrator or use Local Security Policy > Security Settings > Local Policies > User Rights Assignment > Create symbolic links");
1077 }
1078 return true;
1079 }
1080 if (x2 instanceof IOException) {
1081 throw (IOException) x2;
1082 }
1083 throw (IOException) new IOException(x.toString()).initCause(x);
1084 } catch (Exception x) {
1085 throw (IOException) new IOException(x.toString()).initCause(x);
1086 }
1087 }
1088
1089 /**
1090 * @deprecated as of 1.456
1091 * Use {@link #resolveSymlink(File)}
1092 */
1093 public static String resolveSymlink(File link, TaskListener listener) throws InterruptedException, IOException {
1094 return resolveSymlink(link);
1095 }
1096
1097 /**
1098 * Resolves symlink, if the given file is a symlink. Otherwise return null.
1099 * <p>
1100 * If the resolution fails, report an error.
1101 */
1102 public static String resolveSymlink(File link) throws InterruptedException, IOException {
1103 try { // Java 7
1104 Object path = File.class.getMethod("toPath").invoke(link);
1105 return Class.forName("java.nio.file.Files").getMethod("readSymbolicLink", Class.forName("java.nio.file.Path")).invoke(null, path).toString();
1106 } catch (NoSuchMethodException x) {
1107 // fine, Java 5/6; fall through
1108 } catch (InvocationTargetException x) {
1109 Throwable x2 = x.getCause();
1110 if (x2 instanceof UnsupportedOperationException) {
1111 return null; // no symlinks on this platform
1112 }
1113 try {
1114 if (Class.forName("java.nio.file.NotLinkException").isInstance(x2)) {
1115 return null;
1116 }
1117 } catch (ClassNotFoundException x3) {
1118 assert false : x3; // should be Java 7+ here
1119 }
1120 if (x2 instanceof IOException) {
1121 throw (IOException) x2;
1122 }
1123 throw (IOException) new IOException(x.toString()).initCause(x);
1124 } catch (Exception x) {
1125 throw (IOException) new IOException(x.toString()).initCause(x);
1126 }
1127
1128 if(Functions.isWindows()) return null;
1129
1130 String filename = link.getAbsolutePath();
1131 try {
1132 for (int sz=512; sz < 65536; sz*=2) {
1133 Memory m = new Memory(sz);
1134 int r = LIBC.readlink(filename,m,new NativeLong(sz));
1135 if (r<0) {
1136 int err = Native.getLastError();
1137 if (err==22/*EINVAL --- but is this really portable?*/)
1138 return null; // this means it's not a symlink
1139 throw new IOException("Failed to readlink "+link+" error="+ err+" "+ LIBC.strerror(err));
1140 }
1141 if (r==sz)
1142 continue; // buffer too small
1143
1144 byte[] buf = new byte[r];
1145 m.read(0,buf,0,r);
1146 return new String(buf);
1147 }
1148 // something is wrong. It can't be this long!
1149 throw new IOException("Symlink too long: "+link);
1150 } catch (LinkageError e) {
1151 // if JNA is unavailable, fall back.
1152 // we still prefer to try JNA first as PosixAPI supports even smaller platforms.
1153 return PosixAPI.get().readlink(filename);
1154 }
1155 }
1156
1157 /**
1158 * Encodes the URL by RFC 2396.
1159 *
1160 * I thought there's another spec that refers to UTF-8 as the encoding,
1161 * but don't remember it right now.
1162 *
1163 * @since 1.204
1164 * @deprecated since 2008-05-13. This method is broken (see ISSUE#1666). It should probably
1165 * be removed but I'm not sure if it is considered part of the public API
1166 * that needs to be maintained for backwards compatibility.
1167 * Use {@link #encode(String)} instead.
1168 */
1169 @Deprecated
1170 public static String encodeRFC2396(String url) {
1171 try {
1172 return new URI(null,url,null).toASCIIString();
1173 } catch (URISyntaxException e) {
1174 LOGGER.warning("Failed to encode "+url); // could this ever happen?
1175 return url;
1176 }
1177 }
1178
1179 /**
1180 * Wraps with the error icon and the CSS class to render error message.
1181 * @since 1.173
1182 */
1183 public static String wrapToErrorSpan(String s) {
1184 s = "<span class=error><img src='"+
1185 Stapler.getCurrentRequest().getContextPath()+ Jenkins.RESOURCE_PATH+
1186 "/images/none.gif' height=16 width=1>"+s+"</span>";
1187 return s;
1188 }
1189
1190 /**
1191 * Returns the parsed string if parsed successful; otherwise returns the default number.
1192 * If the string is null, empty or a ParseException is thrown then the defaultNumber
1193 * is returned.
1194 * @param numberStr string to parse
1195 * @param defaultNumber number to return if the string can not be parsed
1196 * @return returns the parsed string; otherwise the default number
1197 */
1198 public static Number tryParseNumber(String numberStr, Number defaultNumber) {
1199 if ((numberStr == null) || (numberStr.length() == 0)) {
1200 return defaultNumber;
1201 }
1202 try {
1203 return NumberFormat.getNumberInstance().parse(numberStr);
1204 } catch (ParseException e) {
1205 return defaultNumber;
1206 }
1207 }
1208
1209 /**
1210 * Checks if the public method defined on the base type with the given arguments
1211 * are overridden in the given derived type.
1212 */
1213 public static boolean isOverridden(Class base, Class derived, String methodName, Class... types) {
1214 // the rewriteHudsonWar method isn't overridden.
1215 try {
1216 return !base.getMethod(methodName, types).equals(
1217 derived.getMethod(methodName,types));
1218 } catch (NoSuchMethodException e) {
1219 throw new AssertionError(e);
1220 }
1221 }
1222
1223 /**
1224 * Returns a file name by changing its extension.
1225 *
1226 * @param ext
1227 * For example, ".zip"
1228 */
1229 public static File changeExtension(File dst, String ext) {
1230 String p = dst.getPath();
1231 int pos = p.lastIndexOf('.');
1232 if (pos<0) return new File(p+ext);
1233 else return new File(p.substring(0,pos)+ext);
1234 }
1235
1236 /**
1237 * Null-safe String intern method.
1238 */
1239 public static String intern(String s) {
1240 return s==null ? s : s.intern();
1241 }
1242
1243 /**
1244 * Return true if the systemId denotes an absolute URI .
1245 *
1246 * The same algorithm can be seen in {@link URI}, but
1247 * implementing this by ourselves allow it to be more lenient about
1248 * escaping of URI.
1249 */
1250 public static boolean isAbsoluteUri(String uri) {
1251 int idx = uri.indexOf(':');
1252 if (idx<0) return false; // no ':'. can't be absolute
1253
1254 // #, ?, and / must not be before ':'
1255 return idx<_indexOf(uri, '#') && idx<_indexOf(uri,'?') && idx<_indexOf(uri,'/');
1256 }
1257
1258 /**
1259 * Works like {@link String#indexOf(int)} but 'not found' is returned as s.length(), not -1.
1260 * This enables more straight-forward comparison.
1261 */
1262 private static int _indexOf(String s, char ch) {
1263 int idx = s.indexOf(ch);
1264 if (idx<0) return s.length();
1265 return idx;
1266 }
1267
1268 /**
1269 * Loads a key/value pair string as {@link Properties}
1270 * @since 1.392
1271 */
1272 @IgnoreJRERequirement
1273 public static Properties loadProperties(String properties) throws IOException {
1274 Properties p = new Properties();
1275 try {
1276 p.load(new StringReader(properties));
1277 } catch (NoSuchMethodError e) {
1278 // load(Reader) method is only available on JDK6.
1279 // this fall back version doesn't work correctly with non-ASCII characters,
1280 // but there's no other easy ways out it seems.
1281 p.load(new ByteArrayInputStream(properties.getBytes()));
1282 }
1283 return p;
1284 }
1285
1286 public static final FastDateFormat XS_DATETIME_FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",new SimpleTimeZone(0,"GMT"));
1287
1288 // Note: RFC822 dates must not be localized!
1289 public static final FastDateFormat RFC822_DATETIME_FORMATTER
1290 = FastDateFormat.getInstance("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
1291
1292 private static final Logger LOGGER = Logger.getLogger(Util.class.getName());
1293
1294 /**
1295 * On Unix environment that cannot run "ln", set this to true.
1296 */
1297 public static boolean NO_SYMLINK = Boolean.getBoolean(Util.class.getName()+".noSymLink");
1298
1299 public static boolean SYMLINK_ESCAPEHATCH = Boolean.getBoolean(Util.class.getName()+".symlinkEscapeHatch");
1300 }
1301