All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aragost.javahg.internals.Utils Maven / Gradle / Ivy

/*
 * #%L
 * JavaHg
 * %%
 * Copyright (C) 2011 aragost Trifork ag
 * %%
 * 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.
 * #L%
 */
package com.aragost.javahg.internals;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Collection;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutionException;

import com.aragost.javahg.log.Logger;
import com.aragost.javahg.log.LoggerFactory;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.common.io.Files;

/**
 * Miscellaneous utils methods for JavaHg.
 */
public class Utils {

    private static final Logger LOG = LoggerFactory.getLogger(Utils.class);

    /**
     * Flag indicating if we are on a Windows box
     */
    private static final boolean IS_WINDOWS;

    /**
     * Temp dir where resources are copied from the jar (classpath) to
     * an actual file
     */
    private static final File RESOURCES_TEMP_DIR;
    static {
        String osName = System.getProperty("os.name");
        IS_WINDOWS = osName.startsWith("Windows");

        RESOURCES_TEMP_DIR = Files.createTempDir();
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    Utils.deleteTempDir(RESOURCES_TEMP_DIR);
                    LOG.info("Deleted temp resource dir " + RESOURCES_TEMP_DIR);
                } catch (IOException e) {
                    System.err.println("Could not delete temp resource dir " + RESOURCES_TEMP_DIR);
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 
     * @return true if the OS is Windows, otherwise false
     */
    public static boolean isWindows() {
        return IS_WINDOWS;
    }

    public static Charset getUtf8Charset() {
        return Charset.forName("UTF-8");
    }

    /**
     * Read four bytes from the stream, and return the integer
     * represented by these bytes in big endian. If EOF is reached
     * then -1 is returned.
     * 
     * @param stream
     *            the input stream
     * @return the decoded integer.
     * @throws IOException
     */
    public static int readBigEndian(InputStream stream) throws IOException {
        byte b0 = (byte) stream.read();
        byte b1 = (byte) stream.read();
        byte b2 = (byte) stream.read();
        int b3 = stream.read();
        if (b3 == -1) {
            return -1;
        }
        return (b0 << 24) + ((b1 & 0xFF) << 16) + ((b2 & 0xFF) << 8) + ((byte) b3 & 0xFF);
    }

    /**
     * Write 4 bytes representing the specified integer in big-endian.
     * 
     * @param n
     *            the integer
     * @param stream
     *            the output stream
     * @throws IOException
     */
    public static void writeBigEndian(int n, OutputStream stream) throws IOException {
        byte[] bytes = new byte[] { (byte) (n >>> 24), (byte) (n >>> 16), (byte) (n >>> 8), (byte) n };
        stream.write(bytes);
    }

    /**
     * Read and discard everything from the specified InputStream
     * 
     * @param stream
     * @throws IOException
     */
    public static void consumeAll(InputStream stream) throws IOException {
        while (stream.read(BUF) != -1)
            ;
    }

    private static final byte[] BUF = new byte[256];

    /**
     * Read everything from the stream and return it as a String
     * 
     * @param in
     *            the input stream
     * @param decoder
     *            the {@link CharsetDecoder} encapsulating the policy
     *            of handling errors while decoding.
     * @return the decoded String.
     */
    public static String readStream(InputStream in, CharsetDecoder decoder) {
        try {
            return CharStreams.toString(new InputStreamReader(in, decoder));
        } catch (IOException e) {
            throw new RuntimeIOException(e);
        }
    }

    /**
     * Decode the bytes with the decoder
     * 
     * @param bytes
     * @param decoder
     * @return the decoded bytes
     */
    public static String decodeBytes(byte[] bytes, CharsetDecoder decoder) {
        ByteBuffer wrap = ByteBuffer.wrap(bytes);
        CharBuffer chars;
        try {
            chars = decoder.decode(wrap);
        } catch (CharacterCodingException e) {
            throw asRuntime(e);
        }
        return new String(chars.array(), chars.arrayOffset(), chars.limit());
    }

    /**
     * Insert an element as the first to an array. The input array is
     * not changed, instead a copy is returned.
     * 
     * @param 
     * @param first
     *            element to insert at position 0 in the result.
     * @param rest
     *            elements to insert at position 1 to
     *            {@code rest.length}.
     * @return the unshifted array of length {@code 1 + rest.length}.
     */
    public static  T[] arrayUnshift(T first, T[] rest) {
        @SuppressWarnings("unchecked")
        T[] result = (T[]) Array.newInstance(first.getClass(), 1 + rest.length);
        result[0] = first;
        System.arraycopy(rest, 0, result, 1, rest.length);
        return result;
    }

    /**
     * Concatenate two arrays. The input arrays are copied into the
     * output array.
     * 
     * @param 
     * @param a
     * @param b
     * @return the concatenated array of length
     *         {@code a.length + b.length}.
     */
    public static  T[] arrayConcat(T[] a, T[] b) {
        @SuppressWarnings("unchecked")
        T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length);
        System.arraycopy(a, 0, result, 0, a.length);
        System.arraycopy(b, 0, result, a.length, b.length);
        return result;
    }

    /**
     * Convert an array of Files to an array of Strings.
     * 

* Each file is converted with the getPath() method. * * @param files * @return array of file names as Strings */ public static String[] fileArray2StringArray(File[] files) { String[] result = new String[files.length]; for (int i = 0; i < files.length; i++) { File file = files[i]; result[i] = file.getPath(); } return result; } /** * * @param baseDir * @param strings * @return array of File objects */ public static File[] stringArray2FileArray(File baseDir, String[] strings) { File[] result = new File[strings.length]; for (int i = 0; i < strings.length; i++) { String s = strings[i]; File file = new File(s); if (!file.isAbsolute()) { file = new File(baseDir, s); } result[i] = Utils.resolveSymlinks(file); } return result; } /** * Return a new File object where symlinks are resolved. * * @param file * @return the canonical file name */ public static File resolveSymlinks(File file) { // On windows calling File.getCanonicalFile will result in // abc~ parts will be expanded. We don't want that. if (IS_WINDOWS) { return file; } else { try { return file.getCanonicalFile(); } catch (IOException e) { throw new RuntimeIOException(e); } } } /** * Return the single element in the specified Collection. *

* If the collection is empty null is return * * @param coll * @return null or the singleton element in the collection * @throws IllegalArgumentException * if the collection has more than one element */ public static T single(Collection coll) { switch (coll.size()) { case 0: return null; case 1: return coll.iterator().next(); default: throw new IllegalArgumentException("Collection has more than one element"); } } /** * Create a temporary file. *

* This method is identical to * {@link java.io.File#createTempFile(String, String)} except it * throws a {@link RuntimeIOException}. * * @param suffix * @return the temporary file */ public static File createTempFile(String suffix) { try { return File.createTempFile("javahg-", suffix); } catch (IOException e) { throw new RuntimeIOException(e); } } /** * Determines whether the specified file is a Symbolic Link rather * than an actual file. *

* Will not return true if there is a Symbolic Link anywhere in * the path, only if the specific file is. * * Note: The implementation is adapted from the similar method in * Apache Commons IO * * @param file * the file to check * @return true if the file is a Symbolic Link */ public static boolean isSymlink(File file) { if (IS_WINDOWS) { return false; } try { File fileInCanonicalDir = null; if (file.getParent() == null) { fileInCanonicalDir = file; } else { File canonicalDir = file.getParentFile().getCanonicalFile(); fileInCanonicalDir = new File(canonicalDir, file.getName()); } return !fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile()); } catch (IOException e) { throw new RuntimeIOException(e); } } /** * Convert the specified exception into a RuntimeException * * @param e * @return the RuntimeException */ public static RuntimeException asRuntime(Throwable e) { if (e instanceof RuntimeException) { return (RuntimeException) e; } if (e instanceof ExecutionException) { return asRuntime(e.getCause()); } if (e instanceof InvocationTargetException) { return asRuntime(e.getCause()); } if (e instanceof IOException) { return new RuntimeIOException((IOException) e); } return new RuntimeException(e); } /** * Delete a directory in the system temporary directory * (java.io.tmpdir). * * @throws IOException */ public static void deleteTempDir(File file) throws IOException { file = file.getCanonicalFile(); String javaTmpDir = new File(System.getProperty("java.io.tmpdir")).getCanonicalPath(); if (file.getPath().startsWith(javaTmpDir)) { deleteRecursive(file); } else { throw new IllegalArgumentException("Can only delete files in " + javaTmpDir); } } private static void deleteRecursive(File file) throws IOException { if (file.isDirectory()) { File[] files = file.listFiles(); for (File f : files) { deleteRecursive(f); } } // Windows often fails to delete on first attempt, but after a // short // break the file can be deleted. Wait up to 5 sec. // I believe that it is because the cmdserver process (which // has been // terminated before calling this method) // still has a lock on the dir. So we wait for it to actually // finish and // release lock for (int i = 20; !file.delete(); i--) { if (i == 0) { throw new IOException("Failed to delete: " + file); } try { System.err.println("Sleep a bit and try again to delete " + file + "[" + i + "/20]"); Thread.sleep(250); } catch (InterruptedException e) { } } } /** * Return a File that is created from a resource name found on the * classpath. The resource is written into the File the first time * it's needed. * * @param resourceName * @return the File * @throws IOException */ public static File resourceAsFile(String resourceName) { return resourceAsFile(resourceName, null); } /** * Return a File that is created from a resource name found on the * classpath. The resource is written into the File the first time * it's needed. *

* Patterns of the form %{name} will be replace with the * corresponding value from the replacement map. * * @param resourceName * @parem replacements if null no replacements will be performed * @return the File * @throws IOException */ public static File resourceAsFile(String resourceName, Map replacements) { File file = new File(RESOURCES_TEMP_DIR, resourceName); if (!file.exists()) { file.getParentFile().mkdirs(); InputStream stream = Utils.class.getResourceAsStream(resourceName); try { OutputStream fileOutput = new BufferedOutputStream(new FileOutputStream(file)); OutputStream output = fileOutput; if (replacements != null) { output = new PatternReplacingOutputStream(fileOutput, replacements); } ByteStreams.copy(stream, output); output.close(); } catch (IOException e) { throw new RuntimeIOException(e); } LOG.info("Copy resource {} to {}", resourceName, file.getAbsolutePath()); } return file; } /** * Generate some random bytes that is printable Ascii characters. *

* They will be used in a Mercurial style file. To make it easy to * use the bytes in the style file the characters '"' and '\' will * not be part of the byte array. * * @return */ public static byte[] randomBytes() { byte[] bytes = new byte[20]; new Random().nextBytes(bytes); // Make the bytes printable by modulo 64 and add to '"' and // use 126 for '\\' assert '"' + 64 < 126; for (int i = 0; i < bytes.length; i++) { int b = '"' + 1 + (bytes[i] & 63); if (b == '\\') { b = 126; } bytes[i] = (byte) b; } LOG.info("Random bytes generated: {}", new String(bytes)); return bytes; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy