1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
|
16 | |
|
17 | |
|
18 | |
|
19 | |
|
20 | |
|
21 | |
|
22 | |
|
23 | |
|
24 | |
|
25 | |
|
26 | |
package com.aragost.javahg.internals; |
27 | |
|
28 | |
import java.io.ByteArrayOutputStream; |
29 | |
import java.io.IOException; |
30 | |
import java.io.InputStream; |
31 | |
import java.io.OutputStream; |
32 | |
import java.nio.charset.CharsetDecoder; |
33 | |
import java.util.ArrayList; |
34 | |
import java.util.Arrays; |
35 | |
import java.util.ConcurrentModificationException; |
36 | |
import java.util.List; |
37 | |
import java.util.concurrent.atomic.AtomicReference; |
38 | |
|
39 | |
import com.aragost.javahg.Changeset; |
40 | |
import com.aragost.javahg.DateTime; |
41 | |
import com.aragost.javahg.Repository; |
42 | |
import com.aragost.javahg.UnknownCommandException; |
43 | |
import com.aragost.javahg.commands.CancelledExecutionException; |
44 | |
import com.aragost.javahg.commands.ExecutionException; |
45 | |
import com.google.common.collect.Lists; |
46 | |
import com.google.common.io.ByteStreams; |
47 | |
|
48 | |
|
49 | |
|
50 | |
|
51 | |
|
52 | |
|
53 | |
|
54 | |
|
55 | |
|
56 | |
|
57 | |
|
58 | |
|
59 | |
|
60 | |
|
61 | |
|
62 | |
|
63 | |
public abstract class AbstractCommand { |
64 | |
|
65 | 5 | public enum State { READY, QUEUED, RUNNING, CANCELING }; |
66 | |
|
67 | 7773 | private AtomicReference<State> state = new AtomicReference<AbstractCommand.State>(State.READY); |
68 | |
|
69 | |
private final List<String> cmdLine; |
70 | |
|
71 | |
private final Repository repository; |
72 | |
|
73 | |
private OutputChannelInputStream outputChannelStream; |
74 | |
|
75 | 7773 | private ByteArrayOutputStream error = new ByteArrayOutputStream(); |
76 | |
|
77 | 7773 | private int returnCode = Integer.MIN_VALUE; |
78 | |
|
79 | |
private int lineChannelLength; |
80 | |
|
81 | |
|
82 | |
|
83 | |
|
84 | |
private volatile Server server; |
85 | |
|
86 | 7732 | protected AbstractCommand(Repository repository) { |
87 | 7732 | this.repository = repository; |
88 | 7732 | this.cmdLine = Lists.newArrayList(getCommandName()); |
89 | 7732 | } |
90 | |
|
91 | 41 | protected AbstractCommand(Repository repository, String commandName) { |
92 | 41 | this.repository = repository; |
93 | 41 | this.cmdLine = Lists.newArrayList(commandName); |
94 | 41 | } |
95 | |
|
96 | |
|
97 | |
|
98 | |
|
99 | |
|
100 | |
public abstract String getCommandName(); |
101 | |
|
102 | |
public void cmdAppend(String option) { |
103 | 7684 | this.cmdLine.add(option); |
104 | 7684 | } |
105 | |
|
106 | |
public void cmdAppend(String option, String arg) { |
107 | 2139 | if (arg == null) { |
108 | 1 | throw new NullPointerException("cannot pass null for " + option + " flag"); |
109 | |
} |
110 | 2138 | this.cmdLine.add(option); |
111 | 2138 | this.cmdLine.add(arg); |
112 | 2138 | } |
113 | |
|
114 | |
public void cmdAppend(String option, String[] args) { |
115 | 145 | for (String arg : args) { |
116 | 73 | cmdAppend(option, arg); |
117 | |
} |
118 | 72 | } |
119 | |
|
120 | |
public void cmdAppend(String option, int arg) { |
121 | 4751 | this.cmdLine.add(option); |
122 | 4751 | this.cmdLine.add("" + arg); |
123 | 4751 | } |
124 | |
|
125 | |
public void cmdAppend(String option, DateTime date) { |
126 | 6 | if (date == null) { |
127 | 1 | throw new NullPointerException("cannot pass null for " + option + " flag"); |
128 | |
} |
129 | 5 | this.cmdLine.add(option); |
130 | 5 | this.cmdLine.add(date.getHgString()); |
131 | 5 | } |
132 | |
|
133 | |
|
134 | |
|
135 | |
|
136 | |
|
137 | |
|
138 | |
|
139 | |
|
140 | |
|
141 | |
protected final String launchString(String... args) { |
142 | 1839 | InputStream stdout = launchStream(args); |
143 | |
try { |
144 | 1816 | return Utils.readStream(stdout, getRepository().newDecoder()); |
145 | |
} finally { |
146 | 1814 | cleanUp(); |
147 | |
} |
148 | |
} |
149 | |
|
150 | |
|
151 | |
|
152 | |
|
153 | |
|
154 | |
|
155 | |
|
156 | |
|
157 | |
protected final HgInputStream launchStream(String... args) { |
158 | 3810 | clear(); |
159 | 3810 | changeState(State.READY, State.QUEUED, true); |
160 | |
|
161 | |
try { |
162 | 3809 | server = repository.getServerPool().take(this); |
163 | 3808 | changeState(State.QUEUED, State.RUNNING, true); |
164 | 1 | } catch (InterruptedException e1) { |
165 | 1 | changeState(State.CANCELING, State.READY, false); |
166 | 1 | throw new CancelledExecutionException(this); |
167 | 3808 | } |
168 | |
|
169 | 3808 | List<String> commandLine = new ArrayList<String>(this.cmdLine); |
170 | 3808 | boolean ok = false; |
171 | |
|
172 | 3808 | getRepository().addToCommandLine(commandLine); |
173 | 3808 | commandLine.addAll(Arrays.asList(args)); |
174 | |
|
175 | |
try { |
176 | 3808 | this.outputChannelStream = server.runCommand(commandLine, this); |
177 | 3778 | HgInputStream stream = new HgInputStream(outputChannelStream, this.repository.newDecoder()); |
178 | 3778 | ok = true; |
179 | 3778 | return stream; |
180 | 13 | } catch (UnexpectedServerTerminationException e) { |
181 | 13 | if (state.get() == State.CANCELING) { |
182 | 1 | throw new CancelledExecutionException(this); |
183 | |
} |
184 | 12 | throw e; |
185 | 0 | } catch (IOException e) { |
186 | 0 | throw new RuntimeIOException(e); |
187 | |
} finally { |
188 | |
|
189 | |
|
190 | 3808 | if (!ok && state.get() != State.READY) { |
191 | 18 | state.set(State.READY); |
192 | 18 | repository.getServerPool().abort(server); |
193 | 18 | server = null; |
194 | |
} |
195 | |
} |
196 | |
} |
197 | |
|
198 | |
|
199 | |
|
200 | |
|
201 | |
|
202 | |
|
203 | |
|
204 | |
|
205 | |
|
206 | |
|
207 | |
|
208 | |
|
209 | |
|
210 | |
|
211 | |
|
212 | |
|
213 | |
private void changeState(State current, State next, boolean strict) { |
214 | 11406 | if (!state.compareAndSet(current, next)) { |
215 | 1 | if (state.compareAndSet(State.CANCELING, State.READY)) { |
216 | 1 | throw new CancelledExecutionException(this); |
217 | |
} |
218 | 0 | if (strict) { |
219 | 0 | throw new ConcurrentModificationException("Unexpected command state"); |
220 | |
} |
221 | |
} |
222 | 11405 | } |
223 | |
|
224 | |
protected final LineIterator launchIterator(String... args) { |
225 | 1745 | return new LineIterator(launchStream(args)); |
226 | |
} |
227 | |
|
228 | |
|
229 | |
|
230 | |
|
231 | |
|
232 | |
|
233 | |
|
234 | |
|
235 | |
|
236 | |
|
237 | |
public void reopenOutputChannelStream() { |
238 | 3 | this.outputChannelStream.reopen(); |
239 | 3 | } |
240 | |
|
241 | |
|
242 | |
|
243 | |
|
244 | |
|
245 | |
|
246 | |
|
247 | |
|
248 | |
public void sendLine(String s) { |
249 | 3 | if (this.lineChannelLength == 0) { |
250 | 0 | throw new IllegalStateException("No input expected"); |
251 | |
} |
252 | |
|
253 | 3 | server.sendLine(s); |
254 | 3 | this.lineChannelLength = 0; |
255 | 3 | } |
256 | |
|
257 | |
|
258 | |
|
259 | |
|
260 | |
public boolean needsInputLine() { |
261 | 0 | return this.lineChannelLength > 0; |
262 | |
} |
263 | |
|
264 | |
|
265 | |
|
266 | |
|
267 | |
|
268 | |
|
269 | |
void cleanUp() { |
270 | 1816 | if (this.outputChannelStream != null) { |
271 | |
try { |
272 | 1816 | Utils.consumeAll(this.outputChannelStream); |
273 | 1 | } catch (IOException e) { |
274 | 1 | throw new RuntimeIOException(e); |
275 | 1815 | } |
276 | |
} |
277 | 1815 | } |
278 | |
|
279 | |
protected void clear() { |
280 | 3810 | this.error.reset(); |
281 | 3810 | this.returnCode = Integer.MIN_VALUE; |
282 | 3810 | } |
283 | |
|
284 | |
|
285 | |
|
286 | |
|
287 | |
|
288 | |
|
289 | |
|
290 | |
protected boolean isSuccessful() { |
291 | 3787 | return getReturnCode() == 0; |
292 | |
} |
293 | |
|
294 | |
private String streamAsString(OutputStream stream) { |
295 | 50 | if (stream instanceof ByteArrayOutputStream) { |
296 | 50 | byte[] bytes = ((ByteArrayOutputStream) stream).toByteArray(); |
297 | 50 | CharsetDecoder decoder = getRepository().newDecoder(); |
298 | 50 | return Utils.decodeBytes(bytes, decoder); |
299 | |
} else { |
300 | 0 | throw new IllegalStateException(); |
301 | |
} |
302 | |
} |
303 | |
|
304 | |
|
305 | |
|
306 | |
|
307 | |
public String getErrorString() { |
308 | 50 | return streamAsString(this.error); |
309 | |
} |
310 | |
|
311 | |
|
312 | |
|
313 | |
|
314 | |
public int getReturnCode() { |
315 | 7439 | if (this.returnCode == Integer.MIN_VALUE) { |
316 | 0 | throw new IllegalStateException("cmdserver is still executing request"); |
317 | |
} |
318 | 7439 | return this.returnCode; |
319 | |
} |
320 | |
|
321 | |
|
322 | |
|
323 | |
|
324 | |
public Repository getRepository() { |
325 | 7764 | return repository; |
326 | |
} |
327 | |
|
328 | |
@Override |
329 | |
public String toString() { |
330 | 1 | return getCommandName(); |
331 | |
} |
332 | |
|
333 | |
|
334 | |
|
335 | |
|
336 | |
|
337 | |
|
338 | |
void setLineChannelLength(int lineChannelLength) { |
339 | 3 | this.lineChannelLength = lineChannelLength; |
340 | 3 | } |
341 | |
|
342 | |
|
343 | |
|
344 | |
|
345 | |
|
346 | |
|
347 | |
|
348 | |
|
349 | |
void addToError(BlockInputStream cin) throws IOException { |
350 | 391 | ByteStreams.copy(cin, this.error); |
351 | 391 | } |
352 | |
|
353 | |
protected void withDebugAndChangesetStyle() { |
354 | 32 | withDebugFlag(); |
355 | 32 | cmdAppend("--style", Changeset.CHANGESET_STYLE_PATH); |
356 | 32 | } |
357 | |
|
358 | |
protected void withDebugFlag() { |
359 | 7574 | cmdAppend("--debug"); |
360 | 7574 | } |
361 | |
|
362 | |
|
363 | |
|
364 | |
|
365 | |
|
366 | |
|
367 | |
|
368 | |
final void handleReturnCode(int returnCode) { |
369 | 3789 | this.returnCode = returnCode; |
370 | 3789 | server.clearCurrentCommand(this); |
371 | 3787 | repository.getServerPool().put(server); |
372 | 3787 | server = null; |
373 | 3787 | changeState(State.RUNNING, State.READY, false); |
374 | |
|
375 | 3787 | if (returnCode == -1) { |
376 | |
|
377 | 16 | String errorString = getErrorString(); |
378 | 16 | if (errorString.startsWith("hg: unknown command '")) { |
379 | 1 | throw new UnknownCommandException(this); |
380 | |
} |
381 | |
} |
382 | |
|
383 | 3786 | doneHook(); |
384 | |
|
385 | 3786 | if (!isSuccessful()) { |
386 | 14 | throw new ExecutionException(this); |
387 | |
} |
388 | 3772 | } |
389 | |
|
390 | |
|
391 | |
|
392 | |
|
393 | |
|
394 | |
|
395 | |
public final void cancel() { |
396 | 4 | State oldState = state.getAndSet(State.CANCELING); |
397 | |
|
398 | 4 | if (oldState != State.READY) { |
399 | |
|
400 | |
|
401 | 3 | Server server = this.server; |
402 | |
Process process; |
403 | |
|
404 | 3 | if (server != null && (process = server.getProcess()) != null) { |
405 | 2 | process.destroy(); |
406 | |
} |
407 | |
} |
408 | 4 | } |
409 | |
|
410 | |
|
411 | |
|
412 | |
|
413 | |
State getState() { |
414 | 10758 | return state.get(); |
415 | |
} |
416 | |
|
417 | |
|
418 | |
|
419 | |
|
420 | |
|
421 | |
|
422 | |
|
423 | |
|
424 | |
protected void doneHook() { |
425 | |
|
426 | 3781 | } |
427 | |
} |