Package com.aragost.javahg.internals

Source Code of com.aragost.javahg.internals.Utils

/*
* #%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 <T>
     * @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> 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 <T>
     * @param a
     * @param b
     * @return the concatenated array of length
     *         {@code a.length + b.length}.
     */
    public static <T> 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.
     * <p>
     * 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.
     * <p>
     * If the collection is empty <code>null</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> T single(Collection<T> 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.
     * <p>
     * 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.
     * <p>
     * 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.
     * <p>
     * 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<String, byte[]> 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.
     * <p>
     * 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;
    }

}
TOP

Related Classes of com.aragost.javahg.internals.Utils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.