com.github.maven_nar.NarUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.github.maven_nar.NarUtil.java

Source

/*
 * #%L
 * Native ARchive plugin for Maven
 * %%
 * Copyright (C) 2002 - 2014 NAR Maven Plugin developers.
 * %%
 * 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.
 * #L%
 */
package com.github.maven_nar;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Pattern;

import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.cli.Commandline;

/**
 * @author Mark Donszelmann
 */
public final class NarUtil {
    private static final class StreamGobbler extends Thread {
        private final InputStream is;

        private final TextStream ts;

        private StreamGobbler(final InputStream is, final TextStream ts) {
            this.is = is;
            this.ts = ts;
        }

        @Override
        public void run() {
            try {
                final BufferedReader reader = new BufferedReader(new InputStreamReader(this.is));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    this.ts.println(line);
                }
                reader.close();
            } catch (final IOException e) {
                // e.printStackTrace()
                final StackTraceElement[] stackTrace = e.getStackTrace();
                for (final StackTraceElement element : stackTrace) {
                    this.ts.println(element.toString());
                }
            }
        }
    }

    public static final String DEFAULT_EXCLUDES = "**/*~,**/#*#,**/.#*,**/%*%,**/._*,"
            + "**/CVS,**/CVS/**,**/.cvsignore," + "**/SCCS,**/SCCS/**,**/vssver.scc,"
            + "**/.svn,**/.svn/**,**/.DS_Store";

    public static String addLibraryPathToEnv(final String path, final Map environment, final String os) {
        String pathName = null;
        char separator = ' ';
        switch (os) {
        case OS.WINDOWS:
            pathName = "PATH";
            separator = ';';
            break;
        case OS.MACOSX:
            pathName = "DYLD_LIBRARY_PATH";
            separator = ':';
            break;
        case OS.AIX:
            pathName = "LIBPATH";
            separator = ':';
            break;
        default:
            pathName = "LD_LIBRARY_PATH";
            separator = ':';
            break;
        }

        String value = environment != null ? (String) environment.get(pathName) : null;
        if (value == null) {
            value = NarUtil.getEnv(pathName, pathName, null);
        }

        String libPath = path;
        libPath = libPath.replace(File.pathSeparatorChar, separator);
        if (value != null) {
            value += separator + libPath;
        } else {
            value = libPath;
        }
        if (environment != null) {
            environment.put(pathName, value);
        }
        return pathName + "=" + value;
    }

    /**
     * (Darren) this code lifted from mvn help:active-profiles plugin Recurses
     * into the project's parent poms to find the active profiles of the
     * specified project and all its parents.
     * 
     * @param project
     *          The project to start with
     * @return A list of active profiles
     */
    static List collectActiveProfiles(final MavenProject project) {
        final List profiles = project.getActiveProfiles();

        if (project.hasParent()) {
            profiles.addAll(collectActiveProfiles(project.getParent()));
        }

        return profiles;
    }

    public static int copyDirectoryStructure(final File sourceDirectory, final File destinationDirectory,
            final String includes, final String excludes) throws IOException {
        if (!sourceDirectory.exists()) {
            throw new IOException("Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ").");
        }

        final List files = FileUtils.getFiles(sourceDirectory, includes, excludes);
        final String sourcePath = sourceDirectory.getAbsolutePath();

        int copied = 0;
        for (final Object file1 : files) {
            final File file = (File) file1;
            String dest = file.getAbsolutePath();
            dest = dest.substring(sourcePath.length() + 1);
            final File destination = new File(destinationDirectory, dest);
            if (file.isFile()) {
                // destination = destination.getParentFile();
                // use FileUtils from commons-io, because it preserves timestamps
                org.apache.commons.io.FileUtils.copyFile(file, destination);
                copied++;

                // copy executable bit
                try {
                    // 1.6 only so coded using introspection
                    // destination.setExecutable( file.canExecute(), false );
                    final Method canExecute = file.getClass().getDeclaredMethod("canExecute");
                    final Method setExecutable = destination.getClass().getDeclaredMethod("setExecutable",
                            boolean.class, boolean.class);
                    setExecutable.invoke(destination, canExecute.invoke(file), Boolean.FALSE);
                } catch (final SecurityException | InvocationTargetException | IllegalAccessException
                        | IllegalArgumentException | NoSuchMethodException e) {
                    // ignored
                }
            } else if (file.isDirectory()) {
                if (!destination.exists() && !destination.mkdirs()) {
                    throw new IOException(
                            "Could not create destination directory '" + destination.getAbsolutePath() + "'.");
                }
                copied += copyDirectoryStructure(file, destination, includes, excludes);
            } else {
                throw new IOException("Unknown file type: " + file.getAbsolutePath());
            }
        }
        return copied;
    }

    public static void deleteDirectory(final File dir) throws MojoExecutionException {
        int retries = OS.WINDOWS.equalsIgnoreCase(System.getProperty("os.name")) ? 3 : 1; // Windows file locking (such as due to virus scanners) and sometimes deleting slowly, or slow to report completion.
        while (retries > 0) {
            retries--;
            try {
                FileUtils.deleteDirectory(dir);
                retries = 0;
            } catch (final IOException e) {
                if (retries > 0) {
                    Thread.yield();
                } else {
                    throw new MojoExecutionException("Could not delete directory: " + dir, e);
                }
            }
            if (retries > 0) {
                //      getLog().info("Could not delete directory: " + dir + " : Retrying");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                }
                //TODO: if( windows and interactive ) prompt for retry?
                //@Component(role=org.codehaus.plexus.components.interactivity.Prompter.class, hint="archetype")
                //public class ArchetypePrompter
            }
        }
    }

    static Set findInstallNameToolCandidates(final File[] files, final Log log)
            throws MojoExecutionException, MojoFailureException {
        final HashSet candidates = new HashSet();

        for (final File file2 : files) {
            final File file = file2;

            if (!file.exists()) {
                continue;
            }

            if (file.isDirectory()) {
                candidates.addAll(findInstallNameToolCandidates(file.listFiles(), log));
            }

            final String fileName = file.getName();
            if (file.isFile() && file.canWrite()
                    && (fileName.endsWith(".so") || fileName.endsWith(".dylib") || fileName.endsWith(".jnilib"))) {
                candidates.add(file);
            }
        }

        return candidates;
    }

    // FIXME, should go to AOL.
    /*
     * NOT USED ?
     * public static String getAOLKey( String architecture, String os, Linker
     * linker )
     * throws MojoFailureException, MojoExecutionException
     * {
     * // construct AOL key prefix
     * return getArchitecture( architecture ) + "." + getOS( os ) + "." +
     * getLinkerName( architecture, os, linker )
     * + ".";
     * }
     */

    public static AOL getAOL(final MavenProject project, final String architecture, final String os,
            final Linker linker, final String aol, final Log log)
            throws MojoFailureException, MojoExecutionException {
        // adjust aol
        return aol == null
                ? new AOL(getArchitecture(architecture), getOS(os),
                        getLinkerName(project, architecture, os, linker, log))
                : new AOL(aol);
    }

    public static String getAOLKey(final String aol) {
        // FIXME, this may not always work correctly
        return replace("-", ".", aol);
    }

    public static String getArchitecture(final String architecture) {
        if (architecture == null) {
            return System.getProperty("os.arch");
        }
        return architecture;
    }

    /**
     * Returns the Bcel Class corresponding to the given class filename
     * 
     * @param filename
     *          the absolute file name of the class
     * @return the Bcel Class.
     * @throws IOException
     */
    public static JavaClass getBcelClass(final String filename) throws IOException {
        final ClassParser parser = new ClassParser(filename);
        return parser.parse();
    }

    public static String getEnv(final String envKey, final String alternateSystemProperty,
            final String defaultValue) {
        String envValue = null;
        try {
            envValue = System.getenv(envKey);
            if (envValue == null && alternateSystemProperty != null) {
                envValue = System.getProperty(alternateSystemProperty);
            }
        } catch (final Error e) {
            // JDK 1.4?
            if (alternateSystemProperty != null) {
                envValue = System.getProperty(alternateSystemProperty);
            }
        }

        if (envValue == null) {
            envValue = defaultValue;
        }

        return envValue;
    }

    /**
     * Returns the header file name (javah) corresponding to the given class file
     * name
     * 
     * @param filename
     *          the absolute file name of the class
     * @return the header file name.
     */
    public static String getHeaderName(final String basename, final String filename) {
        final String base = basename.replaceAll("\\\\", "/");
        final String file = filename.replaceAll("\\\\", "/");
        if (!file.startsWith(base)) {
            throw new IllegalArgumentException("Error " + file + " does not start with " + base);
        }
        String header = file.substring(base.length() + 1);
        header = header.replaceAll("/", "_");
        header = header.replaceAll("\\.class", ".h");
        return header;
    }

    public static File getJavaHome(final File javaHome, final String os) {
        File home = javaHome;
        // adjust JavaHome
        if (home == null) {
            home = new File(System.getProperty("java.home"));
            if (home.getName().equals("jre")) {
                // we want the JDK base directory, not the JRE subfolder
                home = home.getParentFile();
            }
        }
        return home;
    }

    public static Linker getLinker(final Linker linker, final Log log) {
        Linker link = linker;
        if (link == null) {
            link = new Linker(log);
        }
        return link;
    }

    public static String getLinkerName(final MavenProject project, final String architecture, final String os,
            final Linker linker, final Log log) throws MojoFailureException, MojoExecutionException {
        return getLinker(linker, log).getName(NarProperties.getInstance(project),
                getArchitecture(architecture) + "." + getOS(os) + ".");
    }

    public static String getOS(final String defaultOs) {
        String os = defaultOs;
        // adjust OS if not given
        if (os == null) {
            os = System.getProperty("os.name");
            final String name = os.toLowerCase();
            if (name.startsWith("windows")) {
                os = OS.WINDOWS;
            }
            if (name.startsWith("linux")) {
                os = OS.LINUX;
            }
            if (name.startsWith("freebsd")) {
                os = OS.FREEBSD;
            }
            if (name.equals("mac os x")) {
                os = OS.MACOSX;
            }
        }
        return os;
    }

    public static boolean isWindows() {
        return Objects.equals(getOS(null), OS.WINDOWS);
    }

    public static void makeExecutable(final File file, final Log log)
            throws MojoExecutionException, MojoFailureException {
        if (!file.exists()) {
            return;
        }

        if (file.isDirectory()) {
            final File[] files = file.listFiles();
            for (final File file2 : files) {
                makeExecutable(file2, log);
            }
        }
        if (file.isFile() && file.canRead() && file.canWrite() && !file.isHidden()) {
            // chmod +x file
            final int result = runCommand("chmod", new String[] { "+x", file.getPath() }, null, null, log);
            if (result != 0) {
                throw new MojoExecutionException("Failed to execute 'chmod +x " + file.getPath() + "'"
                        + " return code: \'" + result + "\'.");
            }
        }
    }

    public static void makeLink(final File file, final Log log)
            throws MojoExecutionException, MojoFailureException {
        if (!file.exists()) {
            return;
        }

        if (file.isDirectory()) {
            final File[] files = file.listFiles();
            for (final File file2 : files) {
                makeLink(file2, log);
            }
        }
        if (file.isFile() && file.canRead() && file.canWrite() && !file.isHidden()
                && file.getName().matches(".*\\.so(\\.\\d+)+$")) {
            final File sofile = new File(file.getParent(),
                    file.getName().substring(0, file.getName().indexOf(".so") + 3));
            if (!sofile.exists()) {
                // ln -s lib.so.xx lib.so
                final int result = runCommand("ln", new String[] { "-s", file.getName(), sofile.getPath() }, null,
                        null, log);
                if (result != 0) {
                    throw new MojoExecutionException("Failed to execute 'ln -s " + file.getName() + " "
                            + sofile.getPath() + "'" + " return code: \'" + result + "\'.");
                }
            }
        }
    }

    /* for jdk 1.4 */
    private static String quote(final String s) {
        final String escQ = "\\Q";
        final String escE = "\\E";

        int slashEIndex = s.indexOf(escE);
        if (slashEIndex == -1) {
            return escQ + s + escE;
        }

        final StringBuffer sb = new StringBuffer(s.length() * 2);
        sb.append(escQ);
        slashEIndex = 0;
        int current = 0;
        while ((slashEIndex = s.indexOf(escE, current)) != -1) {
            sb.append(s.substring(current, slashEIndex));
            current = slashEIndex + 2;
            sb.append(escE);
            sb.append("\\");
            sb.append(escE);
            sb.append(escQ);
        }
        sb.append(s.substring(current, s.length()));
        sb.append(escE);
        return sb.toString();
    }

    /* for jdk 1.4 */
    private static String quoteReplacement(final String s) {
        if (s.indexOf('\\') == -1 && s.indexOf('$') == -1) {
            return s;
        }
        final StringBuffer sb = new StringBuffer();
        for (int i = 0; i < s.length(); i++) {
            final char c = s.charAt(i);
            if (c == '\\') {
                sb.append('\\');
                sb.append('\\');
            } else if (c == '$') {
                sb.append('\\');
                sb.append('$');
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    static void removeNulls(final Collection<?> collection) {
        for (final Iterator<?> iter = collection.iterator(); iter.hasNext();) {
            if (iter.next() == null) {
                iter.remove();
            }
        }
    }

    /**
     * Replaces target with replacement in string. For jdk 1.4 compatiblity.
     * 
     * @param target
     * @param replacement
     * @param string
     * @return
     */
    public static String replace(final CharSequence target, final CharSequence replacement, final String string) {
        return Pattern.compile(quote(target.toString())/*
                                                        * , Pattern.LITERAL jdk 1.4
                                                        */).matcher(string)
                .replaceAll(/* Matcher. jdk 1.4 */quoteReplacement(replacement.toString()));
    }

    public static int runCommand(final String cmd, final String[] args, final File workingDirectory,
            final String[] env, final Log log) throws MojoExecutionException, MojoFailureException {
        if (log.isInfoEnabled()) {
            final StringBuilder argLine = new StringBuilder();
            if (args != null) {
                for (final String arg : args) {
                    argLine.append(" ").append(arg);
                }
            }
            if (workingDirectory != null) {
                log.info("+ cd " + workingDirectory.getAbsolutePath());
            }
            log.info("+ " + cmd + argLine);
        }
        return runCommand(cmd, args, workingDirectory, env, new TextStream() {
            @Override
            public void println(final String text) {
                log.info(text);
            }
        }, new TextStream() {
            @Override
            public void println(final String text) {
                log.error(text);
            }

        }, new TextStream() {
            @Override
            public void println(final String text) {
                log.debug(text);
            }
        }, log);
    }

    public static int runCommand(final String cmd, final String[] args, final File workingDirectory,
            final String[] env, final TextStream out, final TextStream err, final TextStream dbg, final Log log)
            throws MojoExecutionException, MojoFailureException {
        return runCommand(cmd, args, workingDirectory, env, out, err, dbg, log, false);
    }

    public static int runCommand(final String cmd, final String[] args, final File workingDirectory,
            final String[] env, final TextStream out, final TextStream err, final TextStream dbg, final Log log,
            final boolean expectFailure) throws MojoExecutionException, MojoFailureException {
        final Commandline cmdLine = new Commandline();

        try {
            dbg.println("RunCommand: " + cmd);
            cmdLine.setExecutable(cmd);
            if (args != null) {
                for (final String arg : args) {
                    dbg.println("  '" + arg + "'");
                }
                cmdLine.addArguments(args);
            }
            if (workingDirectory != null) {
                dbg.println("in: " + workingDirectory.getPath());
                cmdLine.setWorkingDirectory(workingDirectory);
            }

            if (env != null) {
                dbg.println("with Env:");
                for (final String element : env) {
                    final String[] nameValue = element.split("=", 2);
                    if (nameValue.length < 2) {
                        throw new MojoFailureException("   Misformed env: '" + element + "'");
                    }
                    dbg.println("   '" + nameValue[0] + "=" + nameValue[1] + "'");
                    cmdLine.addEnvironment(nameValue[0], nameValue[1]);
                }
            }

            final Process process = cmdLine.execute();
            final StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), err);
            final StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), out);

            errorGobbler.start();
            outputGobbler.start();
            process.waitFor();
            final int exitValue = process.exitValue();
            dbg.println("ExitValue: " + exitValue);
            final int timeout = 5000;
            errorGobbler.join(timeout);
            outputGobbler.join(timeout);
            if (exitValue != 0 ^ expectFailure) {
                if (log == null) {
                    System.err.println(err.toString());
                    System.err.println(out.toString());
                    System.err.println(dbg.toString());
                } else {
                    log.warn(err.toString());
                    log.warn(out.toString());
                    log.warn(dbg.toString());
                }
                throw new MojoExecutionException("exit code: " + exitValue);
            }
            return exitValue;
        } catch (final MojoExecutionException e) {
            throw e;
        } catch (final Exception e) {
            throw new MojoExecutionException("Could not launch " + cmdLine, e);
        }
    }

    static void runInstallNameTool(final File[] files, final Log log)
            throws MojoExecutionException, MojoFailureException {
        final Set libs = findInstallNameToolCandidates(files, log);

        for (final Object lib1 : libs) {
            final File subjectFile = (File) lib1;
            final String subjectName = subjectFile.getName();
            final String subjectPath = subjectFile.getPath();

            final int idResult = runCommand("install_name_tool", new String[] { "-id", subjectPath, subjectPath },
                    null, null, log);

            if (idResult != 0) {
                throw new MojoExecutionException("Failed to execute 'install_name_tool -id " + subjectPath + " "
                        + subjectPath + "'" + " return code: \'" + idResult + "\'.");
            }

            for (final Object lib : libs) {
                final File dependentFile = (File) lib;
                final String dependentPath = dependentFile.getPath();

                if (Objects.equals(dependentPath, subjectPath)) {
                    continue;
                }

                final int changeResult = runCommand("install_name_tool",
                        new String[] { "-change", subjectName, subjectPath, dependentPath }, null, null, log);

                if (changeResult != 0) {
                    throw new MojoExecutionException(
                            "Failed to execute 'install_name_tool -change " + subjectName + " " + subjectPath + " "
                                    + dependentPath + "'" + " return code: \'" + changeResult + "\'.");
                }
            }
        }
    }

    public static void runRanlib(final File file, final Log log)
            throws MojoExecutionException, MojoFailureException {
        if (!file.exists()) {
            return;
        }

        if (file.isDirectory()) {
            final File[] files = file.listFiles();
            for (final File file2 : files) {
                runRanlib(file2, log);
            }
        }
        if (file.isFile() && file.canWrite() && !file.isHidden() && file.getName().endsWith(".a")) {
            // ranlib file
            final int result = runCommand("ranlib", new String[] { file.getPath() }, null, null, log);
            if (result != 0) {
                throw new MojoExecutionException(
                        "Failed to execute 'ranlib " + file.getPath() + "'" + " return code: \'" + result + "\'.");
            }
        }
    }

    /**
     * Produces a human-readable string of the given object which has fields
     * annotated with the Maven {@link Parameter} annotation.
     * 
     * @param o The object for which a human-readable string is desired.
     * @return A human-readable string, with each {@code @Parameter} field on a
     *         separate line rendered as a key/value pair.
     */
    public static String prettyMavenString(final Object o) {
        final StringBuilder sb = new StringBuilder();
        sb.append(o.getClass().getName()).append(":\n");
        for (final Field f : o.getClass().getDeclaredFields()) {
            if (f.getAnnotation(Parameter.class) == null)
                continue;
            sb.append("\t").append(f.getName()).append("=").append(fieldValue(f, o)).append("\n");
        }
        return sb.toString();
    }

    private static Object fieldValue(final Field f, final Object o) {
        try {
            return f.get(o);
        } catch (final IllegalArgumentException | IllegalAccessException exc) {
            return "<ERROR>";
        }
    }

    private NarUtil() {
        // never instantiate
    }
}