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 public enum State { READY, QUEUED, RUNNING, CANCELING };
66
67 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 private ByteArrayOutputStream error = new ByteArrayOutputStream();
76
77 private int returnCode = Integer.MIN_VALUE;
78
79 private int lineChannelLength;
80
81
82
83
84 private volatile Server server;
85
86 protected AbstractCommand(Repository repository) {
87 this.repository = repository;
88 this.cmdLine = Lists.newArrayList(getCommandName());
89 }
90
91 protected AbstractCommand(Repository repository, String commandName) {
92 this.repository = repository;
93 this.cmdLine = Lists.newArrayList(commandName);
94 }
95
96
97
98
99
100 public abstract String getCommandName();
101
102 public void cmdAppend(String option) {
103 this.cmdLine.add(option);
104 }
105
106 public void cmdAppend(String option, String arg) {
107 if (arg == null) {
108 throw new NullPointerException("cannot pass null for " + option + " flag");
109 }
110 this.cmdLine.add(option);
111 this.cmdLine.add(arg);
112 }
113
114 public void cmdAppend(String option, String[] args) {
115 for (String arg : args) {
116 cmdAppend(option, arg);
117 }
118 }
119
120 public void cmdAppend(String option, int arg) {
121 this.cmdLine.add(option);
122 this.cmdLine.add("" + arg);
123 }
124
125 public void cmdAppend(String option, DateTime date) {
126 if (date == null) {
127 throw new NullPointerException("cannot pass null for " + option + " flag");
128 }
129 this.cmdLine.add(option);
130 this.cmdLine.add(date.getHgString());
131 }
132
133
134
135
136
137
138
139
140
141 protected final String launchString(String... args) {
142 InputStream stdout = launchStream(args);
143 try {
144 return Utils.readStream(stdout, getRepository().newDecoder());
145 } finally {
146 cleanUp();
147 }
148 }
149
150
151
152
153
154
155
156
157 protected final HgInputStream launchStream(String... args) {
158 clear();
159 changeState(State.READY, State.QUEUED, true);
160
161 try {
162 server = repository.getServerPool().take(this);
163 changeState(State.QUEUED, State.RUNNING, true);
164 } catch (InterruptedException e1) {
165 changeState(State.CANCELING, State.READY, false);
166 throw new CancelledExecutionException(this);
167 }
168
169 List<String> commandLine = new ArrayList<String>(this.cmdLine);
170 boolean ok = false;
171
172 getRepository().addToCommandLine(commandLine);
173 commandLine.addAll(Arrays.asList(args));
174
175 try {
176 this.outputChannelStream = server.runCommand(commandLine, this);
177 HgInputStream stream = new HgInputStream(outputChannelStream, this.repository.newDecoder());
178 ok = true;
179 return stream;
180 } catch (UnexpectedServerTerminationException e) {
181 if (state.get() == State.CANCELING) {
182 throw new CancelledExecutionException(this);
183 }
184 throw e;
185 } catch (IOException e) {
186 throw new RuntimeIOException(e);
187 } finally {
188
189
190 if (!ok && state.get() != State.READY) {
191 state.set(State.READY);
192 repository.getServerPool().abort(server);
193 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 if (!state.compareAndSet(current, next)) {
215 if (state.compareAndSet(State.CANCELING, State.READY)) {
216 throw new CancelledExecutionException(this);
217 }
218 if (strict) {
219 throw new ConcurrentModificationException("Unexpected command state");
220 }
221 }
222 }
223
224 protected final LineIterator launchIterator(String... args) {
225 return new LineIterator(launchStream(args));
226 }
227
228
229
230
231
232
233
234
235
236
237 public void reopenOutputChannelStream() {
238 this.outputChannelStream.reopen();
239 }
240
241
242
243
244
245
246
247
248 public void sendLine(String s) {
249 if (this.lineChannelLength == 0) {
250 throw new IllegalStateException("No input expected");
251 }
252
253 server.sendLine(s);
254 this.lineChannelLength = 0;
255 }
256
257
258
259
260 public boolean needsInputLine() {
261 return this.lineChannelLength > 0;
262 }
263
264
265
266
267
268
269 void cleanUp() {
270 if (this.outputChannelStream != null) {
271 try {
272 Utils.consumeAll(this.outputChannelStream);
273 } catch (IOException e) {
274 throw new RuntimeIOException(e);
275 }
276 }
277 }
278
279 protected void clear() {
280 this.error.reset();
281 this.returnCode = Integer.MIN_VALUE;
282 }
283
284
285
286
287
288
289
290 protected boolean isSuccessful() {
291 return getReturnCode() == 0;
292 }
293
294 private String streamAsString(OutputStream stream) {
295 if (stream instanceof ByteArrayOutputStream) {
296 byte[] bytes = ((ByteArrayOutputStream) stream).toByteArray();
297 CharsetDecoder decoder = getRepository().newDecoder();
298 return Utils.decodeBytes(bytes, decoder);
299 } else {
300 throw new IllegalStateException();
301 }
302 }
303
304
305
306
307 public String getErrorString() {
308 return streamAsString(this.error);
309 }
310
311
312
313
314 public int getReturnCode() {
315 if (this.returnCode == Integer.MIN_VALUE) {
316 throw new IllegalStateException("cmdserver is still executing request");
317 }
318 return this.returnCode;
319 }
320
321
322
323
324 public Repository getRepository() {
325 return repository;
326 }
327
328 @Override
329 public String toString() {
330 return getCommandName();
331 }
332
333
334
335
336
337
338 void setLineChannelLength(int lineChannelLength) {
339 this.lineChannelLength = lineChannelLength;
340 }
341
342
343
344
345
346
347
348
349 void addToError(BlockInputStream cin) throws IOException {
350 ByteStreams.copy(cin, this.error);
351 }
352
353 protected void withDebugAndChangesetStyle() {
354 withDebugFlag();
355 cmdAppend("--style", Changeset.CHANGESET_STYLE_PATH);
356 }
357
358 protected void withDebugFlag() {
359 cmdAppend("--debug");
360 }
361
362
363
364
365
366
367
368 final void handleReturnCode(int returnCode) {
369 this.returnCode = returnCode;
370 server.clearCurrentCommand(this);
371 repository.getServerPool().put(server);
372 server = null;
373 changeState(State.RUNNING, State.READY, false);
374
375 if (returnCode == -1) {
376
377 String errorString = getErrorString();
378 if (errorString.startsWith("hg: unknown command '")) {
379 throw new UnknownCommandException(this);
380 }
381 }
382
383 doneHook();
384
385 if (!isSuccessful()) {
386 throw new ExecutionException(this);
387 }
388 }
389
390
391
392
393
394
395 public final void cancel() {
396 State oldState = state.getAndSet(State.CANCELING);
397
398 if (oldState != State.READY) {
399
400
401 Server server = this.server;
402 Process process;
403
404 if (server != null && (process = server.getProcess()) != null) {
405 process.destroy();
406 }
407 }
408 }
409
410
411
412
413 State getState() {
414 return state.get();
415 }
416
417
418
419
420
421
422
423
424 protected void doneHook() {
425
426 }
427 }