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 | } |