com.crunchynoodles.util.FileUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.crunchynoodles.util.FileUtils.java

Source

/***  Java Commons and Niceties Library from CrunchyNoodles.com
 ***  Copyright (C) 2014 in USA by Brian Witt , bwitt@value.net
 ***
 ***  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 languatge governing permissions and
 ***  limitations under the License.
 ***/

/* Code here requires Java 1.7 */

package com.crunchynoodles.util;

import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.google.common.io.*;

//  These are for the directory search method:
import java.nio.file.*;
import java.nio.file.attribute.*;
import static java.nio.file.FileVisitResult.*;

/**
 *  Utilities for files; requires Java 1.7 support.
 *
 * @author brian
 */
public class FileUtils {

    /***
     *  Returns an output stream where written bytes are simply discarded.
     *
     * @return writable stream, hahahahaha...
     */
    public static OutputStream getNullOutputStream() {
        return ByteStreams.nullOutputStream();
    }

    /***
     *  Reads whole file in and returns it as a string, even if file is binary.
     *
     * @param filename filename
     * @param charSet E.g. "UTF-8" or "ISO-8859-1"
     * @return file as whole string, with line endings in tack.
     * @throws FileNotFoundException
     * @throws IOException
     * @see http://stackoverflow.com/questions/326390/how-to-create-a-java-string-from-the-contents-of-a-file
     */
    public static String readWholeFile(String filename, String charSet) throws FileNotFoundException, IOException {
        FileInputStream ins = new FileInputStream(filename);
        String contents = null;
        try {
            contents = readWholeFile(ins, Charset.forName(charSet));
        } finally {
            try {
                ins.close();
            } catch (Exception ex) {
            }
        }

        return contents;
    }

    /***
     *  Converts a {@link FileInputStream} into a big string.
     *  Handles various input-stream character sets, e.g. Latin-3 or UTF-8.
     *
     * @param ins stream to read
     * @param cs character set for single-byte to Unicode conversion.
     * @return A big string
     * @throws IOException
     * @see java.nio.charset.StandardCharsets
     */
    public static String readWholeFile(FileInputStream ins, Charset cs) throws IOException {
        Reader reader = new BufferedReader(new InputStreamReader(ins, cs));
        StringBuilder sb = new StringBuilder(9999);
        char[] buffer = new char[8100];
        int cnt;

        while ((cnt = reader.read(buffer, 0, buffer.length)) > 0) {
            sb.append(buffer, 0, cnt);
        }

        return sb.toString();
    }

    // ----------------------------------------------------------------------------

    /***
     *  Figures out the way to invoke the Arduino build environment.
     *
     * @param foundCmd Filled in with command [0] and any args in [1] to [N-1].
     * @return true when found, false if too obscure.
     */
    public static Boolean getArduinoRunCommand(List<String> foundCmd) {
        String ARDUINO_APPL_NAME = "arduino";

        int[] returnValue = new int[1];
        List<String> check_output = null;
        String[] check_cmd;
        List<String> runit = null;
        String runcmd = null;

        _determineOsBrand();

        try {

            switch (s_osBrand) {
            case OS_WINDOWS_MULTI:
                check_cmd = new String[] { "reg", "QUERY", "HKCU\\Software\\Classes\\Applications\\"
                        + ARDUINO_APPL_NAME + ".exe\\shell\\open\\command" };
                check_output = runCommandGetOutput(null, check_cmd, returnValue);
                if (returnValue[0] != 0)
                    break;

                // should get four lines, line #3: {{    (Default)    REG_SZ    "C:\pkg\Arduino\arduino-1.5.2-windows\arduino-1.5.2\arduino.exe" "%1"}}
                //  Trim away all before double-quote.
                runcmd = check_output.get(2);
                int reg_sz = runcmd.indexOf("REG_SZ");
                if (reg_sz > 0) {
                    runcmd = runcmd.substring(reg_sz + 6).trim();
                    //  String should now be {{"C:\pkg\Arduino\arduino-1.5.2-windows\arduino-1.5.2\arduino.exe" "%1"}}
                    runit = StringUtils.tokenizeQuotedStrings(runcmd);
                }

                break;

            case OS_LINUX:
            case OS_MAC_X86:
            case OS_SOLARIS:
                check_cmd = new String[] { "which", ARDUINO_APPL_NAME };
                check_output = runCommandGetOutput(null, check_cmd, returnValue);
                if (returnValue[0] != 0)
                    break;

                //  should get one line, line #1 {{no arduino in /bin /sbin /usr/bin}} ;
                //  or on success, line #1 {{/usr/bin/arduino}}
                runcmd = check_output.get(0);
                if (runcmd == null || runcmd.startsWith("no " + ARDUINO_APPL_NAME)) {
                    break;
                }

                runit.add(runcmd);
                runit.add("%1");
                break;

            default:
                return false;
            }
        } catch (Exception ex) {
            System.out.println("getArduinoRunCommand() failed: " + ex.getMessage());
        }

        if (runit == null) {
            return false;
        }

        //  Make it just what we have.  Must use addAll() cuz no pass-by-ref in Java.
        foundCmd.clear();
        foundCmd.addAll(runit);

        return true;
    }

    // ----------------------------------------------------------------------------

    /***
     *  Returns the list of serial comm ports, as appropriate for the OS platform.
     *  E.g. in MS-Windows, the list is { "COM1", "COM4" }.
     *  For MacOS, the list  is { "/dev/tty.Bluetooth-Modem", "/dev/tty.Bluetooth-PDA-Sync" }.
     *
     * @return List of strings to use for serial-comm ports, or null if errors.
     * @throws {@code FileNotFoundException} when can't determine any ports to return.
     * @throws {@code IOException} when trouble executing command to find serial-comm ports.
     *
     * @see https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ts_cmd_changeport.mspx?mfr=true
     * @see http://pbxbook.com/other/mac-tty.html
     */
    public List<String> GetCommPortList() throws FileNotFoundException, IOException {
        List<String> comm_ports = new ArrayList<String>();
        int[] retval = new int[1];
        List<String> outs;

        if (FileUtils.isOsKindOf(OS_WINDOWS_MULTI)) {
            try {
                outs = runCommandGetOutput(null, new String[] { "change", "port", "/QUERY" }, retval);
                // for some reason, ERRORLEVEL is 1 after this program runs.
            } catch (InterruptedException ex) {
                //  user hit control-C, just keep going...
                outs = null;
            }
            if (outs == null /* || retval[0] != 0 */ ) {
                throw new FileNotFoundException("change port /QUERY");
            }

            /****
             * Output looks like below.  Grab only the COMn from lines.
             *      AUX = \DosDevices\COM1
             *      COM1 = \Device\Serial0
             *      COM4 = \Device\VCP0
             *      FTDIBUS#VID_0403+PID_6001+A7006R5MA#0000#{4d36e978-e325-11ce-bfc1-08002be10318} = \Device\00000075
             *      FTDIBUS#VID_0403+PID_6001+A7006R5MA#0000#{86e0d1e0-8089-11d0-9ce4-08003e301f73} = \Device\00000075
             */
            for (String line : outs) {
                if (line.startsWith("COM")) {
                    int e = line.indexOf(" = ");
                    if (e > 3) {
                        comm_ports.add(line.substring(0, e));
                    }
                }
            }
        } else if (FileUtils.isOsKindOf(OS_MAC_X86)) {
            MyFilesFinder my_finder = new MyFilesFinder("/dev", "tty.*");
            try {
                my_finder.doIt();
                outs = my_finder.FileList;
            } catch (IOException ex) {
                // oh well, no devices found....
                outs = null;
            }

            comm_ports = outs;
            outs = null;
        }

        return comm_ports;
    }

    /***
     *  Find-files filter class.
     *  Client provides folder and file-glob pattern.
     *  Matches are available in {@code FileList}. <p>
     *
     *  <b>WARNING</b>: {@code PathMatcher} is Java 1.7 !!
     *
     * @see http://docs.oracle.com/javase/tutorial/essential/io/find.html
     */
    class MyFilesFinder extends SimpleFileVisitor<Path> {
        public List<String> FileList;

        PathMatcher m_matcher;
        Path m_starting_dir;

        public MyFilesFinder(String folderName, String globPattern) {
            FileList = new ArrayList<String>();
            m_starting_dir = Paths.get(folderName);
            m_matcher = FileSystems.getDefault().getPathMatcher("glob:" + globPattern);
        }

        public void doIt() throws IOException {
            java.nio.file.Files.walkFileTree(m_starting_dir, this);
        }

        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            // Looking just for files, so no need to check if matching.
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            Path name = file.getFileName();
            if (name != null && m_matcher.matches(name)) {
                FileList.add(file.getFileName().toString());
                System.out.println(file);
            }

            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
            return CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            return CONTINUE;
        }

    }

    // ----------------------------------------------------------------------------

    public static int getOsBrand() {
        if (s_osBrand < 0) {
            _determineOsBrand();
        }

        return s_osBrand;
    }

    /***
     *  See if host OS matches the caller's kind-of interest.
     *  It is a braod-stroke, fuzzy match.
     *  E.g. {@code OS_MAC_PPC} and {@code OS_MAC_X86} are equivalent.
     *
     * @param brand Desired brand to check for.
     * @return {@code true} on OS brand kind-of matches .
     */
    public static boolean isOsKindOf(int brand) {
        int os = getOsBrand();
        switch (brand) {
        case OS_WINDOWS_SINGLE_USER:
        case OS_WINDOWS_MULTI:
            if (os == OS_WINDOWS_SINGLE_USER || os == OS_WINDOWS_MULTI)
                return true;
            break;

        case OS_MAC_PPC:
        case OS_MAC_X86:
            if (os == OS_MAC_PPC || os == OS_MAC_X86)
                return true;
            break;

        case OS_LINUX:
        case OS_SOLARIS:
            //  These have only a single "OS Brand entry", so test for exact match.
            if (os == brand)
                return true;
            break;

        }
        //  Unsure, or don't know the OS type....
        return false;
    }

    public static final int OS_UNKNOWN = -1;
    public static final int OS_WINDOWS_SINGLE_USER = 1;
    public static final int OS_WINDOWS_MULTI = 2;
    public static final int OS_MAC_PPC = 3;
    public static final int OS_MAC_X86 = 4;
    public static final int OS_LINUX = 5; // bsd-like unix.
    public static final int OS_SOLARIS = 6; // SVR4-like unix.

    private static int s_osBrand = -1;

    private static void _determineOsBrand() {
        while (s_osBrand == OS_UNKNOWN) {
            //  Go feel around to determine OS brand.
            if (new File("C:\\Windows\\System32").isDirectory()
                    && new File("C:\\Windows\\Fonts\\arial.ttf").isFile()) {
                s_osBrand = OS_WINDOWS_MULTI;
                break;
            }

            if (new File("/usr/bin").isDirectory() && new File("/etc").isDirectory()) {
                //  this could also by Cygwin under MS-Windows....

                int[] retval = new int[1];
                try {
                    List<String> out = runCommandGetOutput(null, new String[] { "uname", "-a" }, retval);
                    if (retval[0] != 0)
                        break;
                    String[] words = out.get(0).split(" ");
                    if (words[3].equalsIgnoreCase("Darwin")) {
                        s_osBrand = OS_MAC_X86;
                        break;
                    }
                    if (words[3].equalsIgnoreCase("Linux")) {
                        s_osBrand = OS_LINUX;
                        break;
                    }
                } catch (Exception ex) {
                    // do nothing, will default to OS_LINUX below.
                }
            }

            //  oh well, let's just go with Linux as fallback....
            s_osBrand = OS_LINUX;
            break;
        }
    }

    // ----------------------------------------------------------------------------

    /***
     *  Execute process and collect all output.
     *  If exec failure, then returns {@code cmdReturnValue[0]} as -1 and {@code null}
     *  instead of string-array.
     *
     * @param startFolder Initial working directory, or null for user's home-dir.
     * @param args array of tokens to pass, where {@code args[0]} is name of program to run.
     * @param cmdReturnValue - int[0] is return value of process
     * @return captured output as string-list on successful exec, else null if troubles.
     *
     * @see http://stackoverflow.com/questions/1410741/want-to-invoke-a-linux-shell-command-from-java
     */
    public static List<String> runCommandGetOutput(String startFolder, String[] args, int[] cmdReturnValue)
            throws IOException, InterruptedException {
        List<String> commands = new ArrayList<String>();
        commands.addAll(Arrays.asList(args));

        cmdReturnValue[0] = -1;
        if (startFolder == null) {
            startFolder = System.getProperty("user.home");
        }

        //  Run program, gathering all its output.  Combines STDOUT and STDERR together.
        ProcessBuilder pb = new ProcessBuilder(commands);
        pb.directory(new File(startFolder));
        pb.redirectErrorStream(true);
        Process process = pb.start();

        List<String> out = new ArrayList<String>(2000);

        // Use the input stream connected to the normal and error output of the subprocess.
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line;

        //Read output
        while ((line = br.readLine()) != null) {
            out.add(line);
        }

        cmdReturnValue[0] = process.waitFor();

        return out;
    }
}