| 1 | // Jomic - a viewer for comic book archives. |
| 2 | // Copyright (C) 2004-2011 Thomas Aglassinger |
| 3 | // |
| 4 | // This program is free software: you can redistribute it and/or modify |
| 5 | // it under the terms of the GNU General Public License as published by |
| 6 | // the Free Software Foundation, either version 3 of the License, or |
| 7 | // (at your option) any later version. |
| 8 | // |
| 9 | // This program is distributed in the hope that it will be useful, |
| 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | // GNU General Public License for more details. |
| 13 | // |
| 14 | // You should have received a copy of the GNU General Public License |
| 15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 16 | package net.sf.jomic.tools; |
| 17 | |
| 18 | import java.io.File; |
| 19 | import java.io.IOException; |
| 20 | import java.text.NumberFormat; |
| 21 | import java.util.Iterator; |
| 22 | import java.util.LinkedList; |
| 23 | import java.util.List; |
| 24 | import java.util.Locale; |
| 25 | import java.util.regex.Pattern; |
| 26 | |
| 27 | import org.apache.commons.logging.Log; |
| 28 | import org.apache.commons.logging.LogFactory; |
| 29 | |
| 30 | /** |
| 31 | * Utility methods to interact with command line interface. |
| 32 | * |
| 33 | * @author Thomas Aglassinger |
| 34 | */ |
| 35 | public final class ConsoleTools |
| 36 | { |
| 37 | /** |
| 38 | * Pseudo exit code indicating that exit code does not matter. |
| 39 | */ |
| 40 | public static final int NO_RESULT = Integer.MIN_VALUE; |
| 41 | |
| 42 | private static final float NO_VERSION = -1f; |
| 43 | |
| 44 | private static /*@ spec_public nullable @*/ ConsoleTools instance; |
| 45 | |
| 46 | private Log logger; |
| 47 | |
| 48 | private ConsoleTools() { |
| 49 | logger = LogFactory.getLog(ConsoleTools.class); |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * Get accessor to unique instance. |
| 54 | */ |
| 55 | //@ ensures instance != null; |
| 56 | public static synchronized ConsoleTools instance() { |
| 57 | if (instance == null) { |
| 58 | instance = new ConsoleTools(); |
| 59 | } |
| 60 | return instance; |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * Render version as a version number. Unlike <code>Float.toString()</code>, always use a "." |
| 65 | * and never a "," independant of the current locale. |
| 66 | */ |
| 67 | public String asVersionNumber(float version) { |
| 68 | return NumberFormat.getNumberInstance(Locale.US).format(version); |
| 69 | } |
| 70 | |
| 71 | /** |
| 72 | * Extract version from <code>outLines</code>, which are the lines written by a command line |
| 73 | * utility supposed to contain the version number of the tool. |
| 74 | * |
| 75 | * @return the version number or a value < 0 in case no number could be found. |
| 76 | */ |
| 77 | float getVersion(List outLines) { |
| 78 | float result = NO_VERSION; |
| 79 | boolean versionFound = false; |
| 80 | Pattern versionNumberPattern = Pattern.compile("\\d+\\.\\d+"); |
| 81 | Iterator rider = outLines.iterator(); |
| 82 | NumberFormat englishNumberFormat = NumberFormat.getInstance(Locale.ENGLISH); |
| 83 | |
| 84 | while (!versionFound && rider.hasNext()) { |
| 85 | String line = (String) rider.next(); |
| 86 | String[] words = versionNumberPattern.split(line, 2); |
| 87 | |
| 88 | if (words.length == 2) { |
| 89 | int startIndex = words[0].length(); |
| 90 | int endIndex = line.length() - words[1].length(); |
| 91 | String token = line.substring(startIndex, endIndex); |
| 92 | |
| 93 | try { |
| 94 | result = englishNumberFormat.parse(token).floatValue(); |
| 95 | versionFound = true; |
| 96 | logger.info("found unrar version " + result); |
| 97 | } catch (java.text.ParseException error) { |
| 98 | // Ignore error and try with next word. |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | return result; |
| 103 | } |
| 104 | |
| 105 | public float getVersion(String commandPath) |
| 106 | throws IOException, InterruptedException { |
| 107 | float result; |
| 108 | List outLines = run(new String[]{commandPath}, null, NO_RESULT); |
| 109 | |
| 110 | result = getVersion(outLines); |
| 111 | return result; |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Runs the specified (shell) command. |
| 116 | * |
| 117 | * @return the lines written to <code>System.out</code> and <code>System.err</code> |
| 118 | * by the command |
| 119 | * @throws IOException if the exit code is not expectedResult |
| 120 | * @throws InterruptedException |
| 121 | * @param arguments arguments[0] is the actual command, all other entries are the |
| 122 | * arguments passed to it |
| 123 | * @param path the current directory in which the command is to be executed; |
| 124 | * if null, use property "user.dir" (the current working directory) |
| 125 | * @param expectedResult the expected exit code; typically 0 to indicate a successful |
| 126 | * run |
| 127 | */ |
| 128 | //@ requires arguments.length >= 1; |
| 129 | //@ requires \nonnullelements(arguments); |
| 130 | public List run(String[] arguments, /*@ nullable @*/ File path, int expectedResult) |
| 131 | throws IOException, InterruptedException { |
| 132 | return run(arguments, path, expectedResult, null); |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * Runs the specified (shell) command. |
| 137 | * |
| 138 | * @return the lines written to <code>System.out</code> and <code>System.err</code> |
| 139 | * by the command |
| 140 | * @throws IOException if the exit code is not expectedResult |
| 141 | * @throws InterruptedException |
| 142 | * @param arguments arguments[0] is the actual command, all other entries are the |
| 143 | * arguments passed to it |
| 144 | * @param path the current directory in which the command is to be executed; |
| 145 | * if null, use the current working directory (as specified by <code>System.getProperty("user.dir")</code> |
| 146 | * ) |
| 147 | * @param expectedResult the expected exit code; typically 0 to indicate a successful |
| 148 | * run. NO_RESULT means that the exit code should be ignored. |
| 149 | * @param listener to be informed about every line written to <code>System.out</code> |
| 150 | * or <code>Syst.err</code> |
| 151 | */ |
| 152 | //@ requires arguments.length >= 1; |
| 153 | //@ requires \nonnullelements(arguments); |
| 154 | public List run( |
| 155 | String[] arguments, |
| 156 | /*@ nullable @*/ File path, |
| 157 | int expectedResult, |
| 158 | /*@ nullable @*/ ConsoleOutputListener listener) |
| 159 | throws IOException, InterruptedException { |
| 160 | File workingDir; |
| 161 | |
| 162 | if (path == null) { |
| 163 | workingDir = new File(System.getProperty("user.dir")); |
| 164 | } else { |
| 165 | workingDir = path; |
| 166 | } |
| 167 | |
| 168 | List result = new LinkedList(); |
| 169 | |
| 170 | if (logger.isInfoEnabled()) { |
| 171 | logger.info("run command: " + arguments[0]); |
| 172 | logger.info(" in path \"" + workingDir + "\""); |
| 173 | logger.info(" using arguments:"); |
| 174 | for (int i = 1; i < arguments.length; i += 1) { |
| 175 | logger.info(" \"" + arguments[i] + "\""); |
| 176 | } |
| 177 | } |
| 178 | Process process = Runtime.getRuntime().exec(arguments, null, workingDir); |
| 179 | ProcessOutputThread errThread = |
| 180 | new ProcessOutputThread( |
| 181 | process, |
| 182 | process.getErrorStream(), |
| 183 | result, |
| 184 | true, |
| 185 | this, |
| 186 | listener, |
| 187 | ConsoleOutputListener.STREAM_TYPE_ERR); |
| 188 | |
| 189 | errThread.start(); |
| 190 | |
| 191 | Thread outThread = |
| 192 | new ProcessOutputThread( |
| 193 | process, |
| 194 | process.getInputStream(), |
| 195 | result, |
| 196 | false, |
| 197 | this, |
| 198 | listener, |
| 199 | ConsoleOutputListener.STREAM_TYPE_OUT); |
| 200 | |
| 201 | outThread.start(); |
| 202 | |
| 203 | // Wait until the thread to copy the output are done. |
| 204 | if (logger.isDebugEnabled()) { |
| 205 | logger.debug("wait for notify from err"); |
| 206 | } |
| 207 | errThread.join(); |
| 208 | if (logger.isDebugEnabled()) { |
| 209 | logger.debug("wait for notify from out"); |
| 210 | } |
| 211 | outThread.join(); |
| 212 | |
| 213 | // Check the exit code. |
| 214 | int exitCode = process.waitFor(); |
| 215 | |
| 216 | if ((expectedResult != NO_RESULT) && (expectedResult != exitCode)) { |
| 217 | throw new ConsoleIOException(arguments, expectedResult, exitCode, |
| 218 | errThread.getLastLineWritten()); |
| 219 | } |
| 220 | |
| 221 | return result; |
| 222 | } |
| 223 | } |