Util.java 54.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * The MIT License
 *
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
K
kohsuke 已提交
24 25
package hudson;

26 27 28
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
29

30
import edu.umd.cs.findbugs.annotations.SuppressWarnings;
31
import hudson.Proc.LocalProc;
K
kohsuke 已提交
32
import hudson.model.TaskListener;
33
import hudson.os.PosixAPI;
K
kohsuke 已提交
34
import hudson.util.QuotedStringTokenizer;
K
kohsuke 已提交
35
import hudson.util.VariableResolver;
36
import hudson.util.jna.WinIOException;
37

38 39
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.time.FastDateFormat;
K
kohsuke 已提交
40
import org.apache.tools.ant.BuildException;
41
import org.apache.tools.ant.Project;
K
kohsuke 已提交
42 43
import org.apache.tools.ant.taskdefs.Chmod;
import org.apache.tools.ant.taskdefs.Copy;
44
import org.apache.tools.ant.types.FileSet;
45

46 47
import jnr.posix.FileStat;
import jnr.posix.POSIX;
K
kohsuke 已提交
48

49 50
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
51

52
import java.io.*;
53 54
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
55 56
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
K
kohsuke 已提交
57
import java.net.InetAddress;
58 59
import java.net.URI;
import java.net.URISyntaxException;
60
import java.net.UnknownHostException;
61
import java.nio.ByteBuffer;
62 63
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
64
import java.nio.charset.Charset;
65
import java.nio.charset.CharsetEncoder;
66 67
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
68 69
import java.text.NumberFormat;
import java.text.ParseException;
70
import java.util.*;
71
import java.util.concurrent.TimeUnit;
72
import java.util.concurrent.atomic.AtomicBoolean;
K
kohsuke 已提交
73
import java.util.logging.Level;
K
kohsuke 已提交
74
import java.util.logging.Logger;
K
kohsuke 已提交
75 76 77
import java.util.regex.Matcher;
import java.util.regex.Pattern;

A
Andrew Stucki 已提交
78
import hudson.util.jna.Kernel32Utils;
79
import static hudson.util.jna.GNUCLibrary.LIBC;
80

81
import java.security.DigestInputStream;
82

O
Oleg Nenashev 已提交
83 84
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
85
import javax.annotation.Nullable;
86

87
import org.apache.commons.codec.digest.DigestUtils;
K
kohsuke 已提交
88

K
kohsuke 已提交
89
/**
K
kohsuke 已提交
90 91
 * Various utility methods that don't have more proper home.
 *
K
kohsuke 已提交
92 93 94
 * @author Kohsuke Kawaguchi
 */
public class Util {
K
kohsuke 已提交
95

96 97 98 99 100 101 102 103
    // Constant number of milliseconds in various time units.
    private static final long ONE_SECOND_MS = 1000;
    private static final long ONE_MINUTE_MS = 60 * ONE_SECOND_MS;
    private static final long ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
    private static final long ONE_DAY_MS = 24 * ONE_HOUR_MS;
    private static final long ONE_MONTH_MS = 30 * ONE_DAY_MS;
    private static final long ONE_YEAR_MS = 365 * ONE_DAY_MS;

K
kohsuke 已提交
104 105
    /**
     * Creates a filtered sublist.
106
     * @since 1.176
K
kohsuke 已提交
107
     */
O
Oleg Nenashev 已提交
108 109
    @Nonnull
    public static <T> List<T> filter( @Nonnull Iterable<?> base, @Nonnull Class<T> type ) {
K
kohsuke 已提交
110 111 112 113 114 115 116 117
        List<T> r = new ArrayList<T>();
        for (Object i : base) {
            if(type.isInstance(i))
                r.add(type.cast(i));
        }
        return r;
    }

118 119 120
    /**
     * Creates a filtered sublist.
     */
O
Oleg Nenashev 已提交
121 122
    @Nonnull
    public static <T> List<T> filter( @Nonnull List<?> base, @Nonnull Class<T> type ) {
123 124 125
        return filter((Iterable)base,type);
    }

126
    /**
127
     * Pattern for capturing variables. Either $xyz, ${xyz} or ${a.b} but not $a.b, while ignoring "$$"
128
      */
129
    private static final Pattern VARIABLE = Pattern.compile("\\$([A-Za-z0-9_]+|\\{[A-Za-z0-9_.]+\\}|\\$)");
130

K
kohsuke 已提交
131
    /**
K
kohsuke 已提交
132
     * Replaces the occurrence of '$key' by <tt>properties.get('key')</tt>.
K
kohsuke 已提交
133 134
     *
     * <p>
135
     * Unlike shell, undefined variables are left as-is (this behavior is the same as Ant.)
136
     *
K
kohsuke 已提交
137
     */
138
    @Nullable
O
Oleg Nenashev 已提交
139
    public static String replaceMacro( @CheckForNull String s, @Nonnull Map<String,String> properties) {
K
kohsuke 已提交
140 141
        return replaceMacro(s,new VariableResolver.ByMap<String>(properties));
    }
142

K
kohsuke 已提交
143 144 145 146 147 148
    /**
     * Replaces the occurrence of '$key' by <tt>resolver.get('key')</tt>.
     *
     * <p>
     * Unlike shell, undefined variables are left as-is (this behavior is the same as Ant.)
     */
149
    @Nullable
O
Oleg Nenashev 已提交
150
    public static String replaceMacro(@CheckForNull String s, @Nonnull VariableResolver<String> resolver) {
151 152 153
    	if (s == null) {
    		return null;
    	}
154

K
kohsuke 已提交
155
        int idx=0;
156 157 158 159 160 161
        while(true) {
            Matcher m = VARIABLE.matcher(s);
            if(!m.find(idx))   return s;

            String key = m.group().substring(1);

162 163 164 165 166 167 168 169 170
            // escape the dollar sign or get the key to resolve
            String value;
            if(key.charAt(0)=='$') {
               value = "$";
            } else {
               if(key.charAt(0)=='{')  key = key.substring(1,key.length()-1);
               value = resolver.resolve(key);
            }

171
            if(value==null)
172
                idx = m.end(); // skip this
173 174
            else {
                s = s.substring(0,m.start())+value+s.substring(m.end());
175
                idx = m.start() + value.length();
K
kohsuke 已提交
176 177 178 179
            }
        }
    }

K
kohsuke 已提交
180 181 182
    /**
     * Loads the contents of a file into a string.
     */
O
Oleg Nenashev 已提交
183 184
    @Nonnull
    public static String loadFile(@Nonnull File logfile) throws IOException {
185 186 187
        return loadFile(logfile, Charset.defaultCharset());
    }

O
Oleg Nenashev 已提交
188 189
    @Nonnull
    public static String loadFile(@Nonnull File logfile, @Nonnull Charset charset) throws IOException {
K
kohsuke 已提交
190 191 192
        if(!logfile.exists())
            return "";

193
        StringBuilder str = new StringBuilder((int)logfile.length());
K
kohsuke 已提交
194

195
        BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(logfile),charset));
196 197 198 199 200 201 202 203
        try {
            char[] buf = new char[1024];
            int len;
            while((len=r.read(buf,0,buf.length))>0)
               str.append(buf,0,len);
        } finally {
            r.close();
        }
K
kohsuke 已提交
204 205 206 207 208 209 210 211 212 213 214

        return str.toString();
    }

    /**
     * Deletes the contents of the given directory (but not the directory itself)
     * recursively.
     *
     * @throws IOException
     *      if the operation fails.
     */
O
Oleg Nenashev 已提交
215
    public static void deleteContentsRecursive(@Nonnull File file) throws IOException {
K
kohsuke 已提交
216 217 218
        File[] files = file.listFiles();
        if(files==null)
            return;     // the directory didn't exist in the first place
219 220
        for (File child : files)
            deleteRecursive(child);
K
kohsuke 已提交
221 222
    }

223 224 225 226 227
    /**
     * Deletes this file (and does not take no for an answer).
     * @param f a file to delete
     * @throws IOException if it exists but could not be successfully deleted
     */
O
Oleg Nenashev 已提交
228
    public static void deleteFile(@Nonnull File f) throws IOException {
K
kohsuke 已提交
229 230 231 232 233 234
        if (!f.delete()) {
            if(!f.exists())
                // we are trying to delete a file that no longer exists, so this is not an error
                return;

            // perhaps this file is read-only?
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
            makeWritable(f);
            /*
             on Unix both the file and the directory that contains it has to be writable
             for a file deletion to be successful. (Confirmed on Solaris 9)

             $ ls -la
             total 6
             dr-xr-sr-x   2 hudson   hudson       512 Apr 18 14:41 .
             dr-xr-sr-x   3 hudson   hudson       512 Apr 17 19:36 ..
             -r--r--r--   1 hudson   hudson       469 Apr 17 19:36 manager.xml
             -rw-r--r--   1 hudson   hudson         0 Apr 18 14:41 x
             $ rm x
             rm: x not removed: Permission denied
             */

            makeWritable(f.getParentFile());
251

K
kohsuke 已提交
252 253
            if(!f.delete() && f.exists()) {
                // trouble-shooting.
254 255 256 257 258 259 260 261 262 263 264 265
                try {
                    Class.forName("java.nio.file.Files").getMethod("delete", Class.forName("java.nio.file.Path")).invoke(null, File.class.getMethod("toPath").invoke(f));
                } catch (InvocationTargetException x) {
                    Throwable x2 = x.getCause();
                    if (x2 instanceof IOException) {
                        // may have a specific exception message
                        throw (IOException) x2;
                    }
                    // else suppress
                } catch (Throwable x) {
                    // linkage errors, etc.; suppress
                }
K
kohsuke 已提交
266 267 268 269 270
                // see http://www.nabble.com/Sometimes-can%27t-delete-files-from-hudson.scm.SubversionSCM%24CheckOutTask.invoke%28%29-tt17333292.html
                // I suspect other processes putting files in this directory
                File[] files = f.listFiles();
                if(files!=null && files.length>0)
                    throw new IOException("Unable to delete " + f.getPath()+" - files in dir: "+Arrays.asList(files));
271
                throw new IOException("Unable to delete " + f.getPath());
K
kohsuke 已提交
272
            }
K
kohsuke 已提交
273 274 275
        }
    }

276
    /**
277
     * Makes the given file writable by any means possible.
278
     */
O
Oleg Nenashev 已提交
279
    private static void makeWritable(@Nonnull File f) {
280 281
        if (f.setWritable(true)) {
            return;
282
        }
283
        // TODO do we still need to try anything else?
284

285 286 287 288 289 290 291 292 293 294 295
        // try chmod. this becomes no-op if this is not Unix.
        try {
            Chmod chmod = new Chmod();
            chmod.setProject(new Project());
            chmod.setFile(f);
            chmod.setPerm("u+w");
            chmod.execute();
        } catch (BuildException e) {
            LOGGER.log(Level.INFO,"Failed to chmod "+f,e);
        }

296
        try {// try libc chmod
297
            POSIX posix = PosixAPI.jnr();
298 299 300 301 302 303 304
            String path = f.getAbsolutePath();
            FileStat stat = posix.stat(path);
            posix.chmod(path, stat.mode()|0200); // u+w
        } catch (Throwable t) {
            LOGGER.log(Level.FINE,"Failed to chmod(2) "+f,t);
        }

305 306
    }

O
Oleg Nenashev 已提交
307
    public static void deleteRecursive(@Nonnull File dir) throws IOException {
308 309
        if(!isSymlink(dir))
            deleteContentsRecursive(dir);
310 311 312 313 314 315 316 317 318 319
        try {
            deleteFile(dir);
        } catch (IOException e) {
            // if some of the child directories are big, it might take long enough to delete that
            // it allows others to create new files, causing problemsl ike JENKINS-10113
            // so give it one more attempt before we give up.
            if(!isSymlink(dir))
                deleteContentsRecursive(dir);
            deleteFile(dir);
        }
K
kohsuke 已提交
320 321
    }

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
    /*
     * Copyright 2001-2004 The Apache Software Foundation.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    /**
     * Checks if the given file represents a symlink.
     */
    //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
O
Oleg Nenashev 已提交
341
    public static boolean isSymlink(@Nonnull File file) throws IOException {
342 343 344
        Boolean r = isSymlinkJava7(file);
        if (r != null) {
            return r;
345
        }
A
Andrew Stucki 已提交
346
        if (Functions.isWindows()) {
347 348 349 350 351 352 353
            try {
                return Kernel32Utils.isJunctionOrSymlink(file);
            } catch (UnsupportedOperationException e) {
                // fall through
            } catch (LinkageError e) {
                // fall through
            }
A
Andrew Stucki 已提交
354
        }
355 356 357
        String name = file.getName();
        if (name.equals(".") || name.equals(".."))
            return false;
358

359
        File fileInCanonicalParent;
360 361 362 363 364 365 366
        File parentDir = file.getParentFile();
        if ( parentDir == null ) {
            fileInCanonicalParent = file;
        } else {
            fileInCanonicalParent = new File( parentDir.getCanonicalPath(), name );
        }
        return !fileInCanonicalParent.getCanonicalFile().equals( fileInCanonicalParent.getAbsoluteFile() );
367
    }
368

369
    @SuppressWarnings("NP_BOOLEAN_RETURN_NULL")
O
Oleg Nenashev 已提交
370
    private static Boolean isSymlinkJava7(@Nonnull File file) throws IOException {
371 372 373 374
        try {
            Object path = File.class.getMethod("toPath").invoke(file);
            return (Boolean) Class.forName("java.nio.file.Files").getMethod("isSymbolicLink", Class.forName("java.nio.file.Path")).invoke(null, path);
        } catch (NoSuchMethodException x) {
375
            return null; // fine, Java 6
376 377 378 379 380
        } catch (Exception x) {
            throw (IOException) new IOException(x.toString()).initCause(x);
        }
    }

381 382 383 384 385 386
    /**
     * A mostly accurate check of whether a path is a relative path or not. This is designed to take a path against
     * an unknown operating system so may give invalid results.
     *
     * @param path the path.
     * @return {@code true} if the path looks relative.
387
     * @since 1.606
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
     */
    public static boolean isRelativePath(String path) {
        if (path.startsWith("/"))
            return false;
        if (path.startsWith("\\\\") && path.length() > 3 && path.indexOf('\\', 3) != -1)
            return false; // a UNC path which is the most absolute you can get on windows
        if (path.length() >= 3 && ':' == path.charAt(1)) {
            // never mind that the drive mappings can be changed between sessions, we just want to
            // know if the 3rd character is a `\` (or a '/' is acceptable too)
            char p = path.charAt(0);
            if (('A' <= p && p <= 'Z') || ('a' <= p && p <= 'z')) {
                return path.charAt(2) != '\\' && path.charAt(2) != '/';
            }
        }
        return true;
    }

K
kohsuke 已提交
405 406 407 408 409 410 411 412 413 414 415 416
    /**
     * Creates a new temporary directory.
     */
    public static File createTempDir() throws IOException {
        File tmp = File.createTempFile("hudson", "tmp");
        if(!tmp.delete())
            throw new IOException("Failed to delete "+tmp);
        if(!tmp.mkdirs())
            throw new IOException("Failed to create a new directory "+tmp);
        return tmp;
    }

417
    private static final Pattern errorCodeParser = Pattern.compile(".*CreateProcess.*error=([0-9]+).*");
K
kohsuke 已提交
418 419 420 421 422

    /**
     * On Windows, error messages for IOException aren't very helpful.
     * This method generates additional user-friendly error message to the listener
     */
O
Oleg Nenashev 已提交
423
    public static void displayIOException(@Nonnull IOException e, @Nonnull TaskListener listener ) {
K
kohsuke 已提交
424 425 426 427 428
        String msg = getWin32ErrorMessage(e);
        if(msg!=null)
            listener.getLogger().println(msg);
    }

O
Oleg Nenashev 已提交
429 430
    @CheckForNull
    public static String getWin32ErrorMessage(@Nonnull IOException e) {
431 432 433
        return getWin32ErrorMessage((Throwable)e);
    }

K
kohsuke 已提交
434
    /**
435
     * Extracts the Win32 error message from {@link Throwable} if possible.
K
kohsuke 已提交
436 437 438 439
     *
     * @return
     *      null if there seems to be no error code or if the platform is not Win32.
     */
O
Oleg Nenashev 已提交
440
    @CheckForNull
441
    public static String getWin32ErrorMessage(Throwable e) {
K
kohsuke 已提交
442
        String msg = e.getMessage();
443 444 445 446 447 448 449 450 451 452
        if(msg!=null) {
            Matcher m = errorCodeParser.matcher(msg);
            if(m.matches()) {
                try {
                    ResourceBundle rb = ResourceBundle.getBundle("/hudson/win32errors");
                    return rb.getString("error"+m.group(1));
                } catch (Exception _) {
                    // silently recover from resource related failures
                }
            }
453
        }
K
kohsuke 已提交
454

455 456 457
        if(e.getCause()!=null)
            return getWin32ErrorMessage(e.getCause());
        return null; // no message
K
kohsuke 已提交
458 459
    }

460
    /**
461
     * Gets a human readable message for the given Win32 error code.
462 463 464 465
     *
     * @return
     *      null if no such message is available.
     */
O
Oleg Nenashev 已提交
466
    @CheckForNull
467
    public static String getWin32ErrorMessage(int n) {
468 469 470 471 472 473 474
        try {
            ResourceBundle rb = ResourceBundle.getBundle("/hudson/win32errors");
            return rb.getString("error"+n);
        } catch (MissingResourceException e) {
            LOGGER.log(Level.WARNING,"Failed to find resource bundle",e);
            return null;
        }
475 476
    }

K
kohsuke 已提交
477 478 479
    /**
     * Guesses the current host name.
     */
O
Oleg Nenashev 已提交
480
    @Nonnull
K
kohsuke 已提交
481 482 483 484 485 486 487 488
    public static String getHostName() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            return "localhost";
        }
    }

O
Oleg Nenashev 已提交
489
    public static void copyStream(@Nonnull InputStream in,@Nonnull OutputStream out) throws IOException {
K
kohsuke 已提交
490 491
        byte[] buf = new byte[8192];
        int len;
K
Kohsuke Kawaguchi 已提交
492
        while((len=in.read(buf))>=0)
K
kohsuke 已提交
493 494 495
            out.write(buf,0,len);
    }

O
Oleg Nenashev 已提交
496
    public static void copyStream(@Nonnull Reader in, @Nonnull Writer out) throws IOException {
K
kohsuke 已提交
497 498
        char[] buf = new char[8192];
        int len;
K
kohsuke 已提交
499 500 501 502
        while((len=in.read(buf))>0)
            out.write(buf,0,len);
    }

O
Oleg Nenashev 已提交
503
    public static void copyStreamAndClose(@Nonnull InputStream in, @Nonnull OutputStream out) throws IOException {
K
kohsuke 已提交
504 505 506 507 508 509 510 511
        try {
            copyStream(in,out);
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
    }

O
Oleg Nenashev 已提交
512
    public static void copyStreamAndClose(@Nonnull Reader in, @Nonnull Writer out) throws IOException {
K
kohsuke 已提交
513 514 515 516 517 518 519 520
        try {
            copyStream(in,out);
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
    }

K
kohsuke 已提交
521
    /**
K
kohsuke 已提交
522 523 524 525 526 527
     * Tokenizes the text separated by delimiters.
     *
     * <p>
     * In 1.210, this method was changed to handle quotes like Unix shell does.
     * Before that, this method just used {@link StringTokenizer}.
     *
K
kohsuke 已提交
528
     * @since 1.145
K
kohsuke 已提交
529
     * @see QuotedStringTokenizer
K
kohsuke 已提交
530
     */
O
Oleg Nenashev 已提交
531 532
    @Nonnull
    public static String[] tokenize(@Nonnull String s, @CheckForNull String delimiter) {
K
kohsuke 已提交
533
        return QuotedStringTokenizer.tokenize(s,delimiter);
K
kohsuke 已提交
534 535
    }

O
Oleg Nenashev 已提交
536 537
    @Nonnull
    public static String[] tokenize(@Nonnull String s) {
K
kohsuke 已提交
538 539 540
        return tokenize(s," \t\n\r\f");
    }

541 542 543
    /**
     * Converts the map format of the environment variables to the K=V format in the array.
     */
O
Oleg Nenashev 已提交
544 545
    @Nonnull
    public static String[] mapToEnv(@Nonnull Map<String,String> m) {
K
kohsuke 已提交
546 547 548
        String[] r = new String[m.size()];
        int idx=0;

J
jglick 已提交
549 550
        for (final Map.Entry<String,String> e : m.entrySet()) {
            r[idx++] = e.getKey() + '=' + e.getValue();
K
kohsuke 已提交
551 552 553 554
        }
        return r;
    }

O
Oleg Nenashev 已提交
555
    public static int min(int x, @Nonnull int... values) {
K
kohsuke 已提交
556 557 558 559 560 561 562
        for (int i : values) {
            if(i<x)
                x=i;
        }
        return x;
    }

O
Oleg Nenashev 已提交
563 564
    @CheckForNull
    public static String nullify(@CheckForNull String v) {
K
Kohsuke Kawaguchi 已提交
565
        return fixEmpty(v);
K
kohsuke 已提交
566 567
    }

O
Oleg Nenashev 已提交
568 569
    @Nonnull
    public static String removeTrailingSlash(@Nonnull String s) {
K
kohsuke 已提交
570 571 572 573
        if(s.endsWith("/")) return s.substring(0,s.length()-1);
        else                return s;
    }

K
Kohsuke Kawaguchi 已提交
574 575 576 577 578 579 580 581 582 583

    /**
     * Ensure string ends with suffix
     *
     * @param subject Examined string
     * @param suffix  Desired suffix
     * @return Original subject in case it already ends with suffix, null in
     *         case subject was null and subject + suffix otherwise.
     * @since 1.505
     */
584
    @Nullable
O
Oleg Nenashev 已提交
585
    public static String ensureEndsWith(@CheckForNull String subject, @CheckForNull String suffix) {
K
Kohsuke Kawaguchi 已提交
586 587 588 589 590 591 592 593

        if (subject == null) return null;

        if (subject.endsWith(suffix)) return subject;

        return subject + suffix;
    }

K
kohsuke 已提交
594 595 596 597 598
    /**
     * Computes MD5 digest of the given input stream.
     *
     * @param source
     *      The stream will be closed by this method at the end of this method.
K
kohsuke 已提交
599 600
     * @return
     *      32-char wide string
J
Jesse Glick 已提交
601
     * @see DigestUtils#md5Hex(InputStream)
K
kohsuke 已提交
602
     */
O
Oleg Nenashev 已提交
603 604
    @Nonnull
    public static String getDigestOf(@Nonnull InputStream source) throws IOException {
605 606 607 608 609 610 611 612 613 614 615 616 617
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");

            byte[] buffer = new byte[1024];
            DigestInputStream in =new DigestInputStream(source,md5);
            try {
                while(in.read(buffer)>=0)
                    ; // simply discard the input
            } finally {
                in.close();
            }
            return toHexString(md5.digest());
        } catch (NoSuchAlgorithmException e) {
618
            throw new IOException("MD5 not installed",e);    // impossible
619 620
        }
        /* JENKINS-18178: confuses Maven 2 runner
621 622 623 624 625
        try {
            return DigestUtils.md5Hex(source);
        } finally {
            source.close();
        }
626
        */
K
kohsuke 已提交
627
    }
628

O
Oleg Nenashev 已提交
629 630
    @Nonnull
    public static String getDigestOf(@Nonnull String text) {
631
        try {
K
kohsuke 已提交
632
            return getDigestOf(new ByteArrayInputStream(text.getBytes("UTF-8")));
633 634 635 636
        } catch (IOException e) {
            throw new Error(e);
        }
    }
K
kohsuke 已提交
637

638 639 640 641 642 643 644
    /**
     * Computes the MD5 digest of a file.
     * @param file a file
     * @return a 32-character string
     * @throws IOException in case reading fails
     * @since 1.525
     */
O
Oleg Nenashev 已提交
645 646
    @Nonnull
    public static String getDigestOf(@Nonnull File file) throws IOException {
647 648 649 650 651 652 653 654
        InputStream is = new FileInputStream(file);
        try {
            return getDigestOf(new BufferedInputStream(is));
        } finally {
            is.close();
        }
    }

655
    /**
656
     * Converts a string into 128-bit AES key.
657 658
     * @since 1.308
     */
659
    @Nonnull
O
Oleg Nenashev 已提交
660
    public static SecretKey toAes128Key(@Nonnull String s) {
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
        try {
            // turn secretKey into 256 bit hash
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            digest.reset();
            digest.update(s.getBytes("UTF-8"));

            // Due to the stupid US export restriction JDK only ships 128bit version.
            return new SecretKeySpec(digest.digest(),0,128/8, "AES");
        } catch (NoSuchAlgorithmException e) {
            throw new Error(e);
        } catch (UnsupportedEncodingException e) {
            throw new Error(e);
        }
    }

O
Oleg Nenashev 已提交
676 677
    @Nonnull
    public static String toHexString(@Nonnull byte[] data, int start, int len) {
678
        StringBuilder buf = new StringBuilder();
K
kohsuke 已提交
679 680 681 682 683 684 685 686
        for( int i=0; i<len; i++ ) {
            int b = data[start+i]&0xFF;
            if(b<16)    buf.append('0');
            buf.append(Integer.toHexString(b));
        }
        return buf.toString();
    }

O
Oleg Nenashev 已提交
687 688
    @Nonnull
    public static String toHexString(@Nonnull byte[] bytes) {
K
kohsuke 已提交
689 690 691
        return toHexString(bytes,0,bytes.length);
    }

O
Oleg Nenashev 已提交
692 693
    @Nonnull
    public static byte[] fromHexString(@Nonnull String data) {
K
kohsuke 已提交
694 695 696 697 698 699
        byte[] r = new byte[data.length() / 2];
        for (int i = 0; i < data.length(); i += 2)
            r[i / 2] = (byte) Integer.parseInt(data.substring(i, i + 2), 16);
        return r;
    }

K
kohsuke 已提交
700
    /**
K
kohsuke 已提交
701
     * Returns a human readable text of the time duration, for example "3 minutes 40 seconds".
K
i18n  
kohsuke 已提交
702
     * This version should be used for representing a duration of some activity (like build)
K
kohsuke 已提交
703 704 705 706
     *
     * @param duration
     *      number of milliseconds.
     */
O
Oleg Nenashev 已提交
707
    @Nonnull
K
kohsuke 已提交
708
    public static String getTimeSpanString(long duration) {
709 710 711 712 713 714 715 716 717 718 719 720
        // Break the duration up in to units.
        long years = duration / ONE_YEAR_MS;
        duration %= ONE_YEAR_MS;
        long months = duration / ONE_MONTH_MS;
        duration %= ONE_MONTH_MS;
        long days = duration / ONE_DAY_MS;
        duration %= ONE_DAY_MS;
        long hours = duration / ONE_HOUR_MS;
        duration %= ONE_HOUR_MS;
        long minutes = duration / ONE_MINUTE_MS;
        duration %= ONE_MINUTE_MS;
        long seconds = duration / ONE_SECOND_MS;
721 722
        duration %= ONE_SECOND_MS;
        long millisecs = duration;
723 724

        if (years > 0)
C
cactusman 已提交
725
            return makeTimeSpanString(years, Messages.Util_year(years), months, Messages.Util_month(months));
726
        else if (months > 0)
C
cactusman 已提交
727
            return makeTimeSpanString(months, Messages.Util_month(months), days, Messages.Util_day(days));
728
        else if (days > 0)
C
cactusman 已提交
729
            return makeTimeSpanString(days, Messages.Util_day(days), hours, Messages.Util_hour(hours));
730
        else if (hours > 0)
C
cactusman 已提交
731
            return makeTimeSpanString(hours, Messages.Util_hour(hours), minutes, Messages.Util_minute(minutes));
732
        else if (minutes > 0)
C
cactusman 已提交
733
            return makeTimeSpanString(minutes, Messages.Util_minute(minutes), seconds, Messages.Util_second(seconds));
734
        else if (seconds >= 10)
C
cactusman 已提交
735
            return Messages.Util_second(seconds);
736
        else if (seconds >= 1)
737
            return Messages.Util_second(seconds+(float)(millisecs/100)/10); // render "1.2 sec"
738
        else if(millisecs>=100)
739
            return Messages.Util_second((float)(millisecs/10)/100); // render "0.12 sec".
740 741
        else
            return Messages.Util_millisecond(millisecs);
K
kohsuke 已提交
742 743
    }

744 745

    /**
746
     * Create a string representation of a time duration.  If the quantity of
747
     * the most significant unit is big (>=10), then we use only that most
748
     * significant unit in the string representation. If the quantity of the
749 750 751 752 753
     * most significant unit is small (a single-digit value), then we also
     * use a secondary, smaller unit for increased precision.
     * So 13 minutes and 43 seconds returns just "13 minutes", but 3 minutes
     * and 43 seconds is "3 minutes 43 seconds".
     */
O
Oleg Nenashev 已提交
754
    @Nonnull
755
    private static String makeTimeSpanString(long bigUnit,
O
Oleg Nenashev 已提交
756
                                             @Nonnull String bigLabel,
757
                                             long smallUnit,
O
Oleg Nenashev 已提交
758
                                             @Nonnull String smallLabel) {
C
cactusman 已提交
759
        String text = bigLabel;
760
        if (bigUnit < 10)
C
cactusman 已提交
761
            text += ' ' + smallLabel;
762 763 764 765
        return text;
    }


K
i18n  
kohsuke 已提交
766 767
    /**
     * Get a human readable string representing strings like "xxx days ago",
768
     * which should be used to point to the occurrence of an event in the past.
K
i18n  
kohsuke 已提交
769
     */
O
Oleg Nenashev 已提交
770
    @Nonnull
K
i18n  
kohsuke 已提交
771 772 773 774
    public static String getPastTimeString(long duration) {
        return Messages.Util_pastTime(getTimeSpanString(duration));
    }

775

K
kohsuke 已提交
776
    /**
K
kohsuke 已提交
777
     * Combines number and unit, with a plural suffix if needed.
778 779 780
     *
     * @deprecated
     *   Use individual localization methods instead.
781
     *   See {@link Messages#Util_year(Object)} for an example.
782
     *   Deprecated since 2009-06-24, remove method after 2009-12-24.
K
kohsuke 已提交
783
     */
O
Oleg Nenashev 已提交
784
    @Nonnull
785
    @Deprecated
O
Oleg Nenashev 已提交
786
    public static String combine(long n, @Nonnull String suffix) {
K
kohsuke 已提交
787 788
        String s = Long.toString(n)+' '+suffix;
        if(n!=1)
789 790
        	// Just adding an 's' won't work in most natural languages, even English has exception to the rule (e.g. copy/copies).
            s += "s";
K
kohsuke 已提交
791 792 793
        return s;
    }

K
kohsuke 已提交
794 795 796
    /**
     * Create a sub-list by only picking up instances of the specified type.
     */
O
Oleg Nenashev 已提交
797 798
    @Nonnull
    public static <T> List<T> createSubList(@Nonnull Collection<?> source, @Nonnull Class<T> type ) {
K
kohsuke 已提交
799 800 801 802 803 804 805 806
        List<T> r = new ArrayList<T>();
        for (Object item : source) {
            if(type.isInstance(item))
                r.add(type.cast(item));
        }
        return r;
    }

K
kohsuke 已提交
807
    /**
K
kohsuke 已提交
808
     * Escapes non-ASCII characters in URL.
809 810 811 812 813
     *
     * <p>
     * Note that this methods only escapes non-ASCII but leaves other URL-unsafe characters,
     * such as '#'.
     * {@link #rawEncode(String)} should generally be used instead, though be careful to pass only
814
     * a single path component to that method (it will encode /, but this method does not).
K
kohsuke 已提交
815
     */
816
    @Nonnull
O
Oleg Nenashev 已提交
817
    public static String encode(@Nonnull String s) {
K
kohsuke 已提交
818 819 820
        try {
            boolean escaped = false;

821
            StringBuilder out = new StringBuilder(s.length());
K
kohsuke 已提交
822 823 824 825 826

            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            OutputStreamWriter w = new OutputStreamWriter(buf,"UTF-8");

            for (int i = 0; i < s.length(); i++) {
827
                int c = s.charAt(i);
L
lvotypko 已提交
828
                if (c<128 && c!=' ') {
K
kohsuke 已提交
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849
                    out.append((char) c);
                } else {
                    // 1 char -> UTF8
                    w.write(c);
                    w.flush();
                    for (byte b : buf.toByteArray()) {
                        out.append('%');
                        out.append(toDigit((b >> 4) & 0xF));
                        out.append(toDigit(b & 0xF));
                    }
                    buf.reset();
                    escaped = true;
                }
            }

            return escaped ? out.toString() : s;
        } catch (IOException e) {
            throw new Error(e); // impossible
        }
    }

850 851 852
    private static final boolean[] uriMap = new boolean[123];
    static {
        String raw =
853 854
    "!  $ &'()*+,-. 0123456789   =  @ABCDEFGHIJKLMNOPQRSTUVWXYZ    _ abcdefghijklmnopqrstuvwxyz";
  //  "# %         /          :;< >?                           [\]^ `                          {|}~
855 856 857 858 859
  //  ^--so these are encoded
        int i;
        // Encode control chars and space
        for (i = 0; i < 33; i++) uriMap[i] = true;
        for (int j = 0; j < raw.length(); i++, j++)
860
            uriMap[i] = (raw.charAt(j) == ' ');
861 862 863 864
        // If we add encodeQuery() just add a 2nd map to encode &+=
        // queryMap[38] = queryMap[43] = queryMap[61] = true;
    }

865 866
    /**
     * Encode a single path component for use in an HTTP URL.
J
Jesse Glick 已提交
867 868 869 870
     * Escapes all non-ASCII, general unsafe (space and {@code "#%<>[\]^`{|}~})
     * and HTTP special characters ({@code /;:?}) as specified in RFC1738.
     * (so alphanumeric and {@code !@$&*()-_=+',.} are not encoded)
     * Note that slash ({@code /}) is encoded, so the given string should be a
871
     * single path component used in constructing a URL.
872
     * Method name inspired by PHP's rawurlencode.
873
     */
O
Oleg Nenashev 已提交
874 875
    @Nonnull
    public static String rawEncode(@Nonnull String s) {
876 877 878 879 880 881 882
        boolean escaped = false;
        StringBuilder out = null;
        CharsetEncoder enc = null;
        CharBuffer buf = null;
        char c;
        for (int i = 0, m = s.length(); i < m; i++) {
            c = s.charAt(i);
883
            if (c > 122 || uriMap[c]) {
884 885 886 887 888 889 890 891 892 893 894
                if (!escaped) {
                    out = new StringBuilder(i + (m - i) * 3);
                    out.append(s.substring(0, i));
                    enc = Charset.forName("UTF-8").newEncoder();
                    buf = CharBuffer.allocate(1);
                    escaped = true;
                }
                // 1 char -> UTF8
                buf.put(0,c);
                buf.rewind();
                try {
895 896 897
                    ByteBuffer bytes = enc.encode(buf);
                    while (bytes.hasRemaining()) {
                        byte b = bytes.get();
898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913
                        out.append('%');
                        out.append(toDigit((b >> 4) & 0xF));
                        out.append(toDigit(b & 0xF));
                    }
                } catch (CharacterCodingException ex) { }
            } else if (escaped) {
                out.append(c);
            }
        }
        return escaped ? out.toString() : s;
    }

    private static char toDigit(int n) {
        return (char)(n < 10 ? '0' + n : 'A' + n - 10);
    }

K
kohsuke 已提交
914 915 916 917 918 919 920
    /**
     * Surrounds by a single-quote.
     */
    public static String singleQuote(String s) {
        return '\''+s+'\'';
    }

921
    /**
922
     * Escapes HTML unsafe characters like &lt;, &amp; to the respective character entities.
923
     */
924
    @Nonnull
O
Oleg Nenashev 已提交
925
    public static String escape(@Nonnull String text) {
K
kohsuke 已提交
926
        if (text==null)     return null;
927
        StringBuilder buf = new StringBuilder(text.length()+64);
928 929 930 931 932 933 934 935
        for( int i=0; i<text.length(); i++ ) {
            char ch = text.charAt(i);
            if(ch=='\n')
                buf.append("<br>");
            else
            if(ch=='<')
                buf.append("&lt;");
            else
936 937 938
            if(ch=='>')
                buf.append("&gt;");
            else
939 940 941
            if(ch=='&')
                buf.append("&amp;");
            else
S
Seiji Sogabe 已提交
942 943 944 945 946 947
            if(ch=='"')
                buf.append("&quot;");
            else
            if(ch=='\'')
                buf.append("&#039;");
            else
948 949 950 951 952 953 954 955
            if(ch==' ') {
                // All spaces in a block of consecutive spaces are converted to
                // non-breaking space (&nbsp;) except for the last one.  This allows
                // significant whitespace to be retained without prohibiting wrapping.
                char nextCh = i+1 < text.length() ? text.charAt(i+1) : 0;
                buf.append(nextCh==' ' ? "&nbsp;" : " ");
            }
            else
956 957 958 959 960
                buf.append(ch);
        }
        return buf.toString();
    }

O
Oleg Nenashev 已提交
961 962
    @Nonnull
    public static String xmlEscape(@Nonnull String text) {
963
        StringBuilder buf = new StringBuilder(text.length()+64);
K
kohsuke 已提交
964 965 966 967 968
        for( int i=0; i<text.length(); i++ ) {
            char ch = text.charAt(i);
            if(ch=='<')
                buf.append("&lt;");
            else
969 970 971
            if(ch=='>')
                buf.append("&gt;");
            else
K
kohsuke 已提交
972 973 974 975 976 977 978 979
            if(ch=='&')
                buf.append("&amp;");
            else
                buf.append(ch);
        }
        return buf.toString();
    }

K
kohsuke 已提交
980 981 982
    /**
     * Creates an empty file.
     */
O
Oleg Nenashev 已提交
983
    public static void touch(@Nonnull File file) throws IOException {
K
kohsuke 已提交
984 985 986 987 988 989
        new FileOutputStream(file).close();
    }

    /**
     * Copies a single file by using Ant.
     */
O
Oleg Nenashev 已提交
990
    public static void copyFile(@Nonnull File src, @Nonnull File dst) throws BuildException {
K
kohsuke 已提交
991 992 993 994 995 996 997 998 999 1000 1001
        Copy cp = new Copy();
        cp.setProject(new org.apache.tools.ant.Project());
        cp.setTofile(dst);
        cp.setFile(src);
        cp.setOverwrite(true);
        cp.execute();
    }

    /**
     * Convert null to "".
     */
O
Oleg Nenashev 已提交
1002 1003
    @Nonnull
    public static String fixNull(@CheckForNull String s) {
K
kohsuke 已提交
1004 1005 1006 1007 1008 1009 1010
        if(s==null)     return "";
        else            return s;
    }

    /**
     * Convert empty string to null.
     */
O
Oleg Nenashev 已提交
1011 1012
    @CheckForNull
    public static String fixEmpty(@CheckForNull String s) {
K
kohsuke 已提交
1013 1014 1015 1016
        if(s==null || s.length()==0)    return null;
        return s;
    }

K
kohsuke 已提交
1017 1018 1019 1020 1021
    /**
     * Convert empty string to null, and trim whitespace.
     *
     * @since 1.154
     */
O
Oleg Nenashev 已提交
1022 1023
    @CheckForNull
    public static String fixEmptyAndTrim(@CheckForNull String s) {
K
kohsuke 已提交
1024
        if(s==null)    return null;
K
kohsuke 已提交
1025
        return fixEmpty(s.trim());
K
kohsuke 已提交
1026 1027
    }

O
Oleg Nenashev 已提交
1028 1029
    @Nonnull
    public static <T> List<T> fixNull(@CheckForNull List<T> l) {
1030 1031 1032
        return l!=null ? l : Collections.<T>emptyList();
    }

O
Oleg Nenashev 已提交
1033 1034
    @Nonnull
    public static <T> Set<T> fixNull(@CheckForNull Set<T> l) {
1035 1036 1037
        return l!=null ? l : Collections.<T>emptySet();
    }

O
Oleg Nenashev 已提交
1038 1039
    @Nonnull
    public static <T> Collection<T> fixNull(@CheckForNull Collection<T> l) {
1040 1041 1042
        return l!=null ? l : Collections.<T>emptySet();
    }

O
Oleg Nenashev 已提交
1043 1044
    @Nonnull
    public static <T> Iterable<T> fixNull(@CheckForNull Iterable<T> l) {
K
kohsuke 已提交
1045 1046 1047
        return l!=null ? l : Collections.<T>emptySet();
    }

K
kohsuke 已提交
1048 1049 1050
    /**
     * Cuts all the leading path portion and get just the file name.
     */
O
Oleg Nenashev 已提交
1051 1052
    @Nonnull
    public static String getFileName(@Nonnull String filePath) {
K
kohsuke 已提交
1053 1054 1055 1056 1057 1058 1059 1060 1061
        int idx = filePath.lastIndexOf('\\');
        if(idx>=0)
            return getFileName(filePath.substring(idx+1));
        idx = filePath.lastIndexOf('/');
        if(idx>=0)
            return getFileName(filePath.substring(idx+1));
        return filePath;
    }

K
kohsuke 已提交
1062 1063 1064
    /**
     * Concatenate multiple strings by inserting a separator.
     */
O
Oleg Nenashev 已提交
1065 1066
    @Nonnull
    public static String join(@Nonnull Collection<?> strings, @Nonnull String separator) {
K
kohsuke 已提交
1067 1068
        StringBuilder buf = new StringBuilder();
        boolean first=true;
1069
        for (Object s : strings) {
K
kohsuke 已提交
1070
            if(first)   first=false;
1071
            else        buf.append(separator);
K
kohsuke 已提交
1072 1073 1074 1075 1076
            buf.append(s);
        }
        return buf.toString();
    }

K
kohsuke 已提交
1077 1078 1079
    /**
     * Combines all the given collections into a single list.
     */
O
Oleg Nenashev 已提交
1080 1081
    @Nonnull
    public static <T> List<T> join(@Nonnull Collection<? extends T>... items) {
K
kohsuke 已提交
1082 1083 1084 1085 1086 1087 1088 1089 1090
        int size = 0;
        for (Collection<? extends T> item : items)
            size += item.size();
        List<T> r = new ArrayList<T>(size);
        for (Collection<? extends T> item : items)
            r.addAll(item);
        return r;
    }

1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
    /**
     * Creates Ant {@link FileSet} with the base dir and include pattern.
     *
     * <p>
     * The difference with this and using {@link FileSet#setIncludes(String)}
     * is that this method doesn't treat whitespace as a pattern separator,
     * which makes it impossible to use space in the file path.
     *
     * @param includes
     *      String like "foo/bar/*.xml" Multiple patterns can be separated
     *      by ',', and whitespace can surround ',' (so that you can write
     *      "abc, def" and "abc,def" to mean the same thing.
K
kohsuke 已提交
1103
     * @param excludes
K
kohsuke 已提交
1104 1105
     *      Exclusion pattern. Follows the same format as the 'includes' parameter.
     *      Can be null.
K
kohsuke 已提交
1106
     * @since 1.172
1107
     */
O
Oleg Nenashev 已提交
1108 1109
    @Nonnull
    public static FileSet createFileSet(@Nonnull File baseDir, @Nonnull String includes, @CheckForNull String excludes) {
1110 1111 1112
        FileSet fs = new FileSet();
        fs.setDir(baseDir);
        fs.setProject(new Project());
K
kohsuke 已提交
1113 1114 1115 1116

        StringTokenizer tokens;

        tokens = new StringTokenizer(includes,",");
1117 1118 1119 1120
        while(tokens.hasMoreTokens()) {
            String token = tokens.nextToken().trim();
            fs.createInclude().setName(token);
        }
K
kohsuke 已提交
1121 1122 1123 1124 1125 1126 1127
        if(excludes!=null) {
            tokens = new StringTokenizer(excludes,",");
            while(tokens.hasMoreTokens()) {
                String token = tokens.nextToken().trim();
                fs.createExclude().setName(token);
            }
        }
1128 1129
        return fs;
    }
K
kohsuke 已提交
1130

O
Oleg Nenashev 已提交
1131 1132
    @Nonnull
    public static FileSet createFileSet(@Nonnull File baseDir, @Nonnull String includes) {
K
kohsuke 已提交
1133 1134 1135
        return createFileSet(baseDir,includes,null);
    }

1136
    /**
1137
     * Creates a symlink to targetPath at baseDir+symlinkPath.
1138 1139
     * <p>
     * If there's a prior symlink at baseDir+symlinkPath, it will be overwritten.
1140 1141 1142 1143
     *
     * @param baseDir
     *      Base directory to resolve the 'symlinkPath' parameter.
     * @param targetPath
1144
     *      The file that the symlink should point to. Usually relative to the directory of the symlink but may instead be an absolute path.
1145
     * @param symlinkPath
1146
     *      Where to create a symlink in (relative to {@code baseDir})
1147
     */
1148
    public static void createSymlink(@Nonnull File baseDir, @Nonnull String targetPath,
O
Oleg Nenashev 已提交
1149
            @Nonnull String symlinkPath, @Nonnull TaskListener listener) throws InterruptedException {
K
kohsuke 已提交
1150
        try {
1151 1152
            if (createSymlinkJava7(baseDir, targetPath, symlinkPath)) {
                return;
1153
            }
1154
            if (NO_SYMLINK) {
1155 1156
                return;
            }
K
kohsuke 已提交
1157

1158 1159 1160 1161 1162 1163
            File symlinkFile = new File(baseDir, symlinkPath);
            if (Functions.isWindows()) {
                if (symlinkFile.exists()) {
                    symlinkFile.delete();
                }
                File dst = new File(symlinkFile,"..\\"+targetPath);
K
kohsuke 已提交
1164
                try {
1165 1166 1167 1168 1169
                    Kernel32Utils.createSymbolicLink(symlinkFile,targetPath,dst.isDirectory());
                } catch (WinIOException e) {
                    if (e.getErrorCode()==1314) {/* ERROR_PRIVILEGE_NOT_HELD */
                        warnWindowsSymlink();
                        return;
1170
                    }
1171
                    throw e;
K
Kohsuke Kawaguchi 已提交
1172 1173 1174
                } catch (UnsatisfiedLinkError e) {
                    // not available on this Windows
                    return;
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195
                }
            } else {
                String errmsg = "";
                // if a file or a directory exists here, delete it first.
                // try simple delete first (whether exists() or not, as it may be symlink pointing
                // to non-existent target), but fallback to "rm -rf" to delete non-empty dir.
                if (!symlinkFile.delete() && symlinkFile.exists())
                    // ignore a failure.
                    new LocalProc(new String[]{"rm","-rf", symlinkPath},new String[0],listener.getLogger(), baseDir).join();

                Integer r=null;
                if (!SYMLINK_ESCAPEHATCH) {
                    try {
                        r = LIBC.symlink(targetPath,symlinkFile.getAbsolutePath());
                        if (r!=0) {
                            r = Native.getLastError();
                            errmsg = LIBC.strerror(r);
                        }
                    } catch (LinkageError e) {
                        // if JNA is unavailable, fall back.
                        // we still prefer to try JNA first as PosixAPI supports even smaller platforms.
1196 1197
                        POSIX posix = PosixAPI.jnr();
                        if (posix.isNative()) {
1198
                            // TODO should we rethrow PosixException as IOException here?
1199
                            r = posix.symlink(targetPath,symlinkFile.getAbsolutePath());
1200
                        }
1201
                    }
K
kohsuke 已提交
1202
                }
1203 1204
                if (r==null) {
                    // if all else fail, fall back to the most expensive approach of forking a process
1205
                    // TODO is this really necessary? JavaPOSIX should do this automatically
1206 1207 1208 1209 1210 1211
                    r = new LocalProc(new String[]{
                        "ln","-s", targetPath, symlinkPath},
                        new String[0],listener.getLogger(), baseDir).join();
                }
                if (r!=0)
                    listener.getLogger().println(String.format("ln -s %s %s failed: %d %s",targetPath, symlinkFile, r, errmsg));
1212
            }
K
kohsuke 已提交
1213 1214
        } catch (IOException e) {
            PrintStream log = listener.getLogger();
1215
            log.printf("ln %s %s failed%n",targetPath, new File(baseDir, symlinkPath));
K
kohsuke 已提交
1216 1217 1218 1219 1220
            Util.displayIOException(e,listener);
            e.printStackTrace( log );
        }
    }

O
Oleg Nenashev 已提交
1221
    private static boolean createSymlinkJava7(@Nonnull File baseDir, @Nonnull String targetPath, @Nonnull String symlinkPath) throws IOException {
1222 1223 1224 1225 1226
        try {
            Object path = File.class.getMethod("toPath").invoke(new File(baseDir, symlinkPath));
            Object target = Class.forName("java.nio.file.Paths").getMethod("get", String.class, String[].class).invoke(null, targetPath, new String[0]);
            Class<?> filesC = Class.forName("java.nio.file.Files");
            Class<?> pathC = Class.forName("java.nio.file.Path");
1227 1228
            Class<?> fileAlreadyExistsExceptionC = Class.forName("java.nio.file.FileAlreadyExistsException");

1229
            Object noAttrs = Array.newInstance(Class.forName("java.nio.file.attribute.FileAttribute"), 0);
1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248
            final int maxNumberOfTries = 4;
            final int timeInMillis = 100;
            for (int tryNumber = 1; tryNumber <= maxNumberOfTries; tryNumber++) {
                filesC.getMethod("deleteIfExists", pathC).invoke(null, path);
                try {
                    filesC.getMethod("createSymbolicLink", pathC, pathC, noAttrs.getClass()).invoke(null, path, target, noAttrs);
                    break;
                }
                catch (Exception x) {
                    if (fileAlreadyExistsExceptionC.isInstance(x)) {
                        if(tryNumber < maxNumberOfTries) {
                            TimeUnit.MILLISECONDS.sleep(timeInMillis); //trying to defeat likely ongoing race condition
                            continue;
                        }
                        LOGGER.warning("symlink FileAlreadyExistsException thrown "+maxNumberOfTries+" times => cannot createSymbolicLink");
                    }
                    throw x;
                }
            }
1249 1250
            return true;
        } catch (NoSuchMethodException x) {
1251
            return false; // fine, Java 6
1252 1253 1254 1255 1256
        } catch (InvocationTargetException x) {
            Throwable x2 = x.getCause();
            if (x2 instanceof UnsupportedOperationException) {
                return true; // no symlinks on this platform
            }
1257
            if (Functions.isWindows() && String.valueOf(x2).contains("java.nio.file.FileSystemException")) {
1258
                warnWindowsSymlink();
1259 1260
                return true;
            }
1261 1262 1263 1264 1265 1266 1267 1268 1269
            if (x2 instanceof IOException) {
                throw (IOException) x2;
            }
            throw (IOException) new IOException(x.toString()).initCause(x);
        } catch (Exception x) {
            throw (IOException) new IOException(x.toString()).initCause(x);
        }
    }

1270 1271 1272 1273 1274 1275 1276
    private static final AtomicBoolean warnedSymlinks = new AtomicBoolean();
    private static void warnWindowsSymlink() {
        if (warnedSymlinks.compareAndSet(false, true)) {
            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");
        }
    }

K
Kohsuke Kawaguchi 已提交
1277 1278 1279 1280
    /**
     * @deprecated as of 1.456
     *      Use {@link #resolveSymlink(File)}
     */
1281
    @Deprecated
K
Kohsuke Kawaguchi 已提交
1282 1283 1284 1285
    public static String resolveSymlink(File link, TaskListener listener) throws InterruptedException, IOException {
        return resolveSymlink(link);
    }

K
Kohsuke Kawaguchi 已提交
1286 1287 1288 1289 1290 1291
    /**
     * Resolves a symlink to the {@link File} that points to.
     *
     * @return null
     *      if the specified file is not a symlink.
     */
O
Oleg Nenashev 已提交
1292 1293
    @CheckForNull
    public static File resolveSymlinkToFile(@Nonnull File link) throws InterruptedException, IOException {
K
Kohsuke Kawaguchi 已提交
1294 1295 1296 1297 1298 1299 1300
        String target = resolveSymlink(link);
        if (target==null)   return null;

        File f = new File(target);
        if (f.isAbsolute()) return f;   // absolute symlink
        return new File(link.getParentFile(),target);   // relative symlink
    }
K
Kohsuke Kawaguchi 已提交
1301

K
kohsuke 已提交
1302 1303 1304 1305
    /**
     * Resolves symlink, if the given file is a symlink. Otherwise return null.
     * <p>
     * If the resolution fails, report an error.
K
Kohsuke Kawaguchi 已提交
1306 1307 1308 1309 1310 1311
     *
     * @return
     *      null if the given file is not a symlink.
     *      If the symlink is absolute, the returned string is an absolute path.
     *      If the symlink is relative, the returned string is that relative representation.
     *      The relative path is meant to be resolved from the location of the symlink.
K
kohsuke 已提交
1312
     */
O
Oleg Nenashev 已提交
1313 1314
    @CheckForNull
    public static String resolveSymlink(@Nonnull File link) throws InterruptedException, IOException {
1315
        try { // Java 7
1316 1317 1318
            Object path = File.class.getMethod("toPath").invoke(link);
            return Class.forName("java.nio.file.Files").getMethod("readSymbolicLink", Class.forName("java.nio.file.Path")).invoke(null, path).toString();
        } catch (NoSuchMethodException x) {
1319
            // fine, Java 6; fall through
1320 1321 1322 1323 1324
        } catch (InvocationTargetException x) {
            Throwable x2 = x.getCause();
            if (x2 instanceof UnsupportedOperationException) {
                return null; // no symlinks on this platform
            }
1325 1326 1327 1328 1329 1330 1331
            try {
                if (Class.forName("java.nio.file.NotLinkException").isInstance(x2)) {
                    return null;
                }
            } catch (ClassNotFoundException x3) {
                assert false : x3; // should be Java 7+ here
            }
1332 1333 1334 1335
            if (x2.getClass().getName().equals("java.nio.file.FileSystemException")) {
                // Thrown ("Incorrect function.") on JDK 7u21 in Windows 2012 when called on a non-symlink, rather than NotLinkException, contrary to documentation. Maybe only when not on NTFS?
                return null;
            }
1336 1337 1338 1339 1340 1341 1342 1343
            if (x2 instanceof IOException) {
                throw (IOException) x2;
            }
            throw (IOException) new IOException(x.toString()).initCause(x);
        } catch (Exception x) {
            throw (IOException) new IOException(x.toString()).initCause(x);
        }

1344
        if(Functions.isWindows())     return null;
K
kohsuke 已提交
1345 1346 1347 1348 1349

        String filename = link.getAbsolutePath();
        try {
            for (int sz=512; sz < 65536; sz*=2) {
                Memory m = new Memory(sz);
1350
                int r = LIBC.readlink(filename,m,new NativeLong(sz));
K
kohsuke 已提交
1351
                if (r<0) {
K
bug fix  
kohsuke 已提交
1352 1353
                    int err = Native.getLastError();
                    if (err==22/*EINVAL --- but is this really portable?*/)
K
kohsuke 已提交
1354
                        return null; // this means it's not a symlink
1355
                    throw new IOException("Failed to readlink "+link+" error="+ err+" "+ LIBC.strerror(err));
K
kohsuke 已提交
1356 1357 1358 1359 1360 1361 1362
                }
                if (r==sz)
                    continue;   // buffer too small

                byte[] buf = new byte[r];
                m.read(0,buf,0,r);
                return new String(buf);
1363
            }
K
kohsuke 已提交
1364 1365 1366 1367 1368
            // something is wrong. It can't be this long!
            throw new IOException("Symlink too long: "+link);
        } catch (LinkageError e) {
            // if JNA is unavailable, fall back.
            // we still prefer to try JNA first as PosixAPI supports even smaller platforms.
1369
            return PosixAPI.jnr().readlink(filename);
1370 1371 1372
        }
    }

1373 1374 1375 1376 1377 1378 1379
    /**
     * Encodes the URL by RFC 2396.
     *
     * I thought there's another spec that refers to UTF-8 as the encoding,
     * but don't remember it right now.
     *
     * @since 1.204
M
mindless 已提交
1380
     * @deprecated since 2008-05-13. This method is broken (see ISSUE#1666). It should probably
1381 1382
     * be removed but I'm not sure if it is considered part of the public API
     * that needs to be maintained for backwards compatibility.
1383
     * Use {@link #encode(String)} instead.
1384
     */
1385
    @Deprecated
1386 1387 1388 1389 1390 1391 1392 1393 1394
    public static String encodeRFC2396(String url) {
        try {
            return new URI(null,url,null).toASCIIString();
        } catch (URISyntaxException e) {
            LOGGER.warning("Failed to encode "+url);    // could this ever happen?
            return url;
        }
    }

K
kohsuke 已提交
1395 1396
    /**
     * Wraps with the error icon and the CSS class to render error message.
1397
     * @since 1.173
K
kohsuke 已提交
1398
     */
O
Oleg Nenashev 已提交
1399 1400
    @Nonnull
    public static String wrapToErrorSpan(@Nonnull String s) {
1401
        s = "<span class=error style='display:inline-block'>"+s+"</span>";
K
kohsuke 已提交
1402 1403
        return s;
    }
1404

1405 1406 1407 1408 1409 1410 1411 1412
    /**
     * Returns the parsed string if parsed successful; otherwise returns the default number.
     * If the string is null, empty or a ParseException is thrown then the defaultNumber
     * is returned.
     * @param numberStr string to parse
     * @param defaultNumber number to return if the string can not be parsed
     * @return returns the parsed string; otherwise the default number
     */
O
Oleg Nenashev 已提交
1413 1414
    @CheckForNull
    public static Number tryParseNumber(@CheckForNull String numberStr, @CheckForNull Number defaultNumber) {
1415 1416 1417 1418 1419 1420 1421 1422 1423
        if ((numberStr == null) || (numberStr.length() == 0)) {
            return defaultNumber;
        }
        try {
            return NumberFormat.getNumberInstance().parse(numberStr);
        } catch (ParseException e) {
            return defaultNumber;
        }
    }
K
kohsuke 已提交
1424

1425
    /**
1426 1427
     * Checks if the method defined on the base type with the given arguments
     * is overridden in the given derived type.
1428
     */
O
Oleg Nenashev 已提交
1429
    public static boolean isOverridden(@Nonnull Class base, @Nonnull Class derived, @Nonnull String methodName, @Nonnull Class... types) {
1430 1431 1432 1433 1434
        return !getMethod(base, methodName, types).equals(getMethod(derived, methodName, types));
    }

    private static Method getMethod(@Nonnull Class clazz, @Nonnull String methodName, @Nonnull Class... types) {
        Method res = null;
1435
        try {
1436 1437 1438 1439 1440 1441
            res = clazz.getDeclaredMethod(methodName, types);
            // private, static or final methods can not be overridden
            if (res != null && (Modifier.isPrivate(res.getModifiers()) || Modifier.isFinal(res.getModifiers()) 
                    || Modifier.isStatic(res.getModifiers()))) {
                res = null;
            }
1442
        } catch (NoSuchMethodException e) {
1443 1444 1445 1446 1447 1448
            // Method not found in clazz, let's search in superclasses
            Class superclass = clazz.getSuperclass();
            if (superclass != null) {
                res = getMethod(superclass, methodName, types);
            }
        } catch (SecurityException e) {
1449 1450
            throw new AssertionError(e);
        }
1451 1452 1453 1454 1455
        if (res == null) {
            throw new IllegalArgumentException(
                    String.format("Method %s not found in %s (or it is private, final or static)", methodName, clazz.getName()));
        }
        return res;
1456 1457
    }

1458 1459 1460 1461 1462 1463
    /**
     * Returns a file name by changing its extension.
     *
     * @param ext
     *      For example, ".zip"
     */
O
Oleg Nenashev 已提交
1464 1465
    @Nonnull
    public static File changeExtension(@Nonnull File dst, @Nonnull String ext) {
1466 1467 1468 1469 1470 1471
        String p = dst.getPath();
        int pos = p.lastIndexOf('.');
        if (pos<0)  return new File(p+ext);
        else        return new File(p.substring(0,pos)+ext);
    }

K
kohsuke 已提交
1472 1473
    /**
     * Null-safe String intern method.
1474
     * @return A canonical representation for the string object. Null for null input strings
K
kohsuke 已提交
1475
     */
1476
    @Nullable
O
Oleg Nenashev 已提交
1477
    public static String intern(@CheckForNull String s) {
K
kohsuke 已提交
1478 1479 1480
        return s==null ? s : s.intern();
    }

K
Kohsuke Kawaguchi 已提交
1481 1482 1483 1484 1485 1486 1487
    /**
     * Return true if the systemId denotes an absolute URI .
     *
     * The same algorithm can be seen in {@link URI}, but
     * implementing this by ourselves allow it to be more lenient about
     * escaping of URI.
     */
O
Oleg Nenashev 已提交
1488
    public static boolean isAbsoluteUri(@Nonnull String uri) {
K
Kohsuke Kawaguchi 已提交
1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499
        int idx = uri.indexOf(':');
        if (idx<0)  return false;   // no ':'. can't be absolute

        // #, ?, and / must not be before ':'
        return idx<_indexOf(uri, '#') && idx<_indexOf(uri,'?') && idx<_indexOf(uri,'/');
    }

    /**
     * Works like {@link String#indexOf(int)} but 'not found' is returned as s.length(), not -1.
     * This enables more straight-forward comparison.
     */
O
Oleg Nenashev 已提交
1500
    private static int _indexOf(@Nonnull String s, char ch) {
K
Kohsuke Kawaguchi 已提交
1501 1502 1503 1504 1505
        int idx = s.indexOf(ch);
        if (idx<0)  return s.length();
        return idx;
    }

1506 1507 1508 1509
    /**
     * Loads a key/value pair string as {@link Properties}
     * @since 1.392
     */
O
Oleg Nenashev 已提交
1510 1511
    @Nonnull
    public static Properties loadProperties(@Nonnull String properties) throws IOException {
1512
        Properties p = new Properties();
1513
        p.load(new StringReader(properties));
1514 1515 1516
        return p;
    }

1517
    public static final FastDateFormat XS_DATETIME_FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",new SimpleTimeZone(0,"GMT"));
K
kohsuke 已提交
1518

1519
    // Note: RFC822 dates must not be localized!
1520 1521
    public static final FastDateFormat RFC822_DATETIME_FORMATTER
            = FastDateFormat.getInstance("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
K
kohsuke 已提交
1522 1523

    private static final Logger LOGGER = Logger.getLogger(Util.class.getName());
1524 1525 1526 1527

    /**
     * On Unix environment that cannot run "ln", set this to true.
     */
1528
    public static boolean NO_SYMLINK = SystemProperties.getBoolean(Util.class.getName()+".noSymLink");
1529

1530
    public static boolean SYMLINK_ESCAPEHATCH = SystemProperties.getBoolean(Util.class.getName()+".symlinkEscapeHatch");
K
kohsuke 已提交
1531
}