001: //Copyright (c) Janna Khegai 2004, Hans-Joachim Daniels 2005
002: //
003: //This program is free software; you can redistribute it and/or modify
004: //it under the terms of the GNU General Public License as published by
005: //the Free Software Foundation; either version 2 of the License, or
006: //(at your option) any later version.
007: //
008: //This program is distributed in the hope that it will be useful,
009: //but WITHOUT ANY WARRANTY; without even the implied warranty of
010: //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
011: //GNU General Public License for more details.
012: //
013: //You can either finde the file LICENSE or LICENSE.TXT in the source
014: //distribution or in the .jar file of this application
015:
016: package de.uka.ilkd.key.ocl.gf;
017:
018: import java.io.*;
019: import java.util.Vector;
020: import java.util.logging.Level;
021: import java.util.logging.Logger;
022:
023: import javax.swing.JFrame;
024: import javax.swing.JOptionPane;
025: import javax.swing.ProgressMonitor;
026:
027: class GfCapsule {
028: /**
029: * XML parsing debug messages
030: */
031: private static Logger xmlLogger = Logger.getLogger(GfCapsule.class
032: .getName()
033: + ".xml");
034: /**
035: * generic logging of this class
036: */
037: private static Logger logger = Logger.getLogger(GfCapsule.class
038: .getName());
039: /**
040: * The output from GF is in here.
041: * Only the read methods, initializeGF and the prober objects access this.
042: */
043: BufferedReader fromProc;
044: /**
045: * Used to leave messages for GF here.
046: * But <b>only</b> in send and special probers that clean up with undo
047: * after them (or don't change the state like PrintnameLoader).
048: */
049: BufferedWriter toProc;
050:
051: /**
052: * Starts GF with the given command gfcmd in another process.
053: * Sets up the reader and writer to that process.
054: * Does in it self not read anything from GF.
055: * @param gfcmd The complete command to start GF, including 'gf' itself.
056: */
057: public GfCapsule(String[] gfcmd) {
058: try {
059: Process extProc = Runtime.getRuntime().exec(gfcmd);
060: InputStreamReader isr = new InputStreamReader(extProc
061: .getInputStream(), "UTF8");
062: this .fromProc = new BufferedReader(isr);
063: String defaultEncoding = isr.getEncoding();
064: if (logger.isLoggable(Level.FINER)) {
065: logger.finer("encoding " + defaultEncoding);
066: }
067: this .toProc = new BufferedWriter(new OutputStreamWriter(
068: extProc.getOutputStream(), "UTF8"));
069: } catch (IOException e) {
070: JOptionPane.showMessageDialog(new JFrame(),
071: "Could not start " + gfcmd + "\nCheck your $PATH",
072: "Error", JOptionPane.ERROR_MESSAGE);
073: throw new RuntimeException("Could not start " + gfcmd
074: + "\nCheck your $PATH");
075: }
076: }
077:
078: /**
079: * Does the actual writing of command to the GF process via STDIN
080: * @param command exactly the string that is going to be sent
081: */
082: protected void realSend(String command) {
083: try {
084: toProc.write(command, 0, command.length());
085: toProc.newLine();
086: toProc.flush();
087: } catch (IOException e) {
088: System.err.println("Could not write to external process "
089: + e);
090: }
091:
092: }
093:
094: /**
095: * reads the part between >gfinit< and >/gfinit<
096: * @return the data for the new category menu
097: */
098: protected NewCategoryMenuResult readGfinit() {
099: try {
100: //read <hmsg> or <newcat> or <topic> (in case of no grammar loaded)
101: String readresult = fromProc.readLine();
102: if (xmlLogger.isLoggable(Level.FINER))
103: xmlLogger.finer("12 " + readresult);
104: //when old grammars are loaded, the first line looks like
105: //"reading grammar of old format letter.Abs.gfreading old file letter.Abs.gf<gfinit>"
106: if (readresult.indexOf("<gfinit>") > -1) {
107: readresult = fromProc.readLine();
108: if (xmlLogger.isLoggable(Level.FINER))
109: xmlLogger.finer("12 " + readresult);
110: }
111: //no command appendix expected or applicable here, so appendix is discarded
112: Hmsg hmsg = readHmsg(readresult);
113: String next = hmsg.lastline;
114: //no hmsg supported here. Wouldn't be applicable.
115: //the reading above is to silently ignore it intead of failing.
116: //formHmsg(hmsg);
117:
118: if ((next != null)
119: && ((next.indexOf("newcat") > -1) || (next
120: .indexOf("topic") > -1))) {
121: NewCategoryMenuResult ncmr = readNewMenu();
122: return ncmr;
123: }
124:
125: } catch (IOException e) {
126: System.err
127: .println("Could not read from external process:\n"
128: + e);
129: }
130: return null;
131: }
132:
133: /**
134: * reads the greeting text from GF
135: * @return S tuple with first = the last read GF line,
136: * which should be the first loading line
137: * and second = The greetings string
138: */
139: protected StringTuple readGfGreetings() {
140: try {
141: String readresult = "";
142: StringBuffer outputStringBuffer = new StringBuffer();
143: readresult = fromProc.readLine();
144: if (xmlLogger.isLoggable(Level.FINER))
145: xmlLogger.finer("1 " + readresult);
146: while ((readresult.indexOf("gf") == -1)
147: && (readresult.trim().indexOf("<") < 0)) {
148: outputStringBuffer.append(readresult).append("\n");
149: readresult = fromProc.readLine();
150: if (xmlLogger.isLoggable(Level.FINER))
151: xmlLogger.finer("1 " + readresult);
152: }
153: return new StringTuple(readresult, outputStringBuffer
154: .toString());
155: } catch (IOException e) {
156: System.err
157: .println("Could not read from external process:\n"
158: + e);
159: return new StringTuple("", e.getLocalizedMessage());
160: }
161:
162: }
163:
164: /**
165: * reads the loading and compiling messages from GF
166: * @param readresult the first loading line
167: * @param pm to monitor the loading progress. May be null
168: * @return A tuple with first = the first line from >gfinit< or >gfedit<
169: * and second = the loading message as pure text
170: */
171: protected StringTuple readGfLoading(String readresult,
172: ProgressMonitor pm) {
173: try {
174: // in case nothing has been loaded first, the that has to be done now
175: if (readresult == null) {
176: readresult = fromProc.readLine();
177: if (xmlLogger.isLoggable(Level.FINER))
178: xmlLogger.finer("1 " + readresult);
179: }
180: StringBuffer textPure = new StringBuffer();
181: int progress = 5300;
182: while (!(readresult.indexOf("<gfinit>") > -1 || (readresult
183: .indexOf("<gfmenu>") > -1))) {
184: readresult = fromProc.readLine();
185: if (readresult == null) {
186: throw new IOException(
187: "GF: unexpected end of output");
188: }
189: if (xmlLogger.isLoggable(Level.FINER))
190: xmlLogger.finer("1 " + readresult);
191: textPure.append(readresult).append("\n");
192: progress += 12;
193: Utils.tickProgress(pm, progress, null);
194: }
195: //when old grammars are loaded, the first line looks like
196: //"reading grammar of old format letter.Abs.gfreading old file letter.Abs.gf<gfinit>"
197: //without newlines
198: final int beginInit = readresult.indexOf("<gfinit>");
199: if (beginInit > 0) {
200: textPure.append(readresult.substring(0, beginInit))
201: .append("\n");
202: //that is the expected result
203: readresult = "<gfinit>";
204: }
205: return new StringTuple(readresult, textPure.toString());
206: } catch (IOException e) {
207: System.err
208: .println("Could not read from external process:\n"
209: + e);
210: return new StringTuple("", e.getLocalizedMessage());
211: }
212:
213: }
214:
215: /**
216: * Reads the <gfedit> part from GF's XML output.
217: * The different subtags are put into the result
218: * @param newObj If a new object in the editor has been started.
219: * If the to-be-read hmsg contains the newObject flag,
220: * that overwrites this parameter
221: * @return the read tags, partially halfy parsed, partially raw.
222: * The way the different form methods expect it.
223: */
224: protected GfeditResult readGfedit(boolean newObj) {
225: try {
226: String next = "";
227: //read <gfedit>
228: String readresult = fromProc.readLine();
229: if (xmlLogger.isLoggable(Level.FINER))
230: xmlLogger.finer("11 " + readresult);
231: //read either <hsmg> or <lineatization>
232: readresult = fromProc.readLine();
233: if (xmlLogger.isLoggable(Level.FINER))
234: xmlLogger.finer("11 " + readresult);
235:
236: //hmsg stuff
237: final Hmsg hmsg = readHmsg(readresult);
238: next = hmsg.lastline;
239:
240: //reading <linearizations>
241: //seems to be the only line read here
242: //this is here to give as some sort of catch clause.
243: while ((next != null)
244: && ((next.length() == 0) || (next
245: .indexOf("<linearizations>") == -1))) {
246: next = fromProc.readLine();
247: if (next != null) {
248: if (xmlLogger.isLoggable(Level.FINER))
249: xmlLogger.finer("10 " + next);
250: } else {
251: System.exit(0);
252: }
253: }
254: readresult = next;
255: String lin = readLin();
256: final String treeString = readTree();
257: final String message = readMessage();
258: //read the menu stuff
259: Vector gfCommandVector;
260: if (newObj || hmsg.newObject) {
261: gfCommandVector = readRefinementMenu();
262: } else {
263: while (readresult.indexOf("</menu") == -1) {
264: readresult = fromProc.readLine();
265: if (xmlLogger.isLoggable(Level.FINER))
266: xmlLogger.finer("12 " + readresult);
267: }
268: gfCommandVector = null;
269: }
270: // "" should occur quite fast, but it has not already been read,
271: // since the last read line is "</menu>"
272: for (int i = 0; i < 3 && !readresult.equals(""); i++) {
273: readresult = fromProc.readLine();
274: if (xmlLogger.isLoggable(Level.FINER))
275: xmlLogger.finer("11 " + readresult);
276: }
277: //all well, return the read stuff
278: return new GfeditResult(gfCommandVector, hmsg, lin,
279: message, treeString);
280:
281: } catch (IOException e) {
282: System.err
283: .println("Could not read from external process:\n"
284: + e);
285: }
286: //nothing well, return bogus stuff
287: return new GfeditResult(new Vector(), new Hmsg("", "", false,
288: false, false, false, true), "", "", "");
289:
290: }
291:
292: /**
293: * reads the linearizations in all language.
294: * seems to expect the first line of the XML structure
295: * (< lin) already to be read
296: * Accumulates the GF-output between <linearization> </linearization> tags
297: */
298: protected String readLin() {
299: StringBuffer lins = new StringBuffer();
300: try {
301: //read <linearizations>
302: String readresult = fromProc.readLine();
303: if (xmlLogger.isLoggable(Level.FINER))
304: xmlLogger.finer("7 " + readresult);
305: lins.append(readresult).append('\n');
306: readresult = fromProc.readLine();
307: if (xmlLogger.isLoggable(Level.FINER))
308: xmlLogger.finer("6 " + readresult);
309: while ((readresult != null)
310: && (readresult.indexOf("/linearization") == -1)) {
311: lins.append(readresult).append('\n');
312: readresult = fromProc.readLine();
313: if (xmlLogger.isLoggable(Level.FINER))
314: xmlLogger.finer("6 " + readresult);
315: }
316: } catch (IOException e) {
317: System.err.println(e.getMessage());
318: e.printStackTrace();
319: }
320: return lins.toString();
321: }
322:
323: /**
324: * reads in the tree and calls formTree without start end end tag of tree
325: * expects the first starting XML tag tree to be already read
326: * @return the read tags for the tree or null if a read error occurs
327: */
328: protected String readTree() {
329: String treeString = "";
330: try {
331: //read <tree>
332: String readresult = fromProc.readLine();
333: if (xmlLogger.isLoggable(Level.FINER))
334: xmlLogger.finer("6 " + readresult);
335: readresult = fromProc.readLine();
336: if (xmlLogger.isLoggable(Level.FINER))
337: xmlLogger.finer("6 " + readresult);
338: while (readresult.indexOf("/tree") == -1) {
339: treeString += readresult + "\n";
340: readresult = fromProc.readLine();
341: if (xmlLogger.isLoggable(Level.FINER))
342: xmlLogger.finer("6 " + readresult);
343: }
344: return treeString;
345: } catch (IOException e) {
346: System.err.println(e.getMessage());
347: e.printStackTrace();
348: return null;
349: }
350: }
351:
352: /**
353: * Parses the GF-output between <message> </message> tags
354: * and returns it.
355: * @return The read message.
356: */
357: protected String readMessage() {
358: String s = "";
359: try {
360: // read <message>
361: String readresult = fromProc.readLine();
362: if (xmlLogger.isLoggable(Level.FINER))
363: xmlLogger.finer("6 " + readresult);
364: readresult = fromProc.readLine();
365: if (xmlLogger.isLoggable(Level.FINER))
366: xmlLogger.finer("7 " + readresult);
367: while (readresult.indexOf("/message") == -1) {
368: s += readresult + "\n";
369: readresult = fromProc.readLine();
370: if (xmlLogger.isLoggable(Level.FINER))
371: xmlLogger.finer("7 " + readresult);
372: }
373: return s;
374: } catch (IOException e) {
375: System.err.println(e.getLocalizedMessage());
376: e.printStackTrace();
377: return e.getLocalizedMessage();
378: }
379: }
380:
381: /**
382: * reads the cat entries and puts them into result.menuContent,
383: * after that reads
384: * the names of the languages and puts them into the result.languages
385: * The loaded grammar files are put into result.paths,
386: * a guessed grammar name into result.grammarName
387: * Parses the GF-output between <gfinit> tags
388: */
389: protected NewCategoryMenuResult readNewMenu() {
390: //here the read stuff is sorted into
391: String grammarName = "";
392: final Vector languages = new Vector();
393: final Vector menuContent = new Vector();
394: final Vector paths = new Vector();
395:
396: boolean more = true;
397: try {
398: //read first cat
399: String readresult = fromProc.readLine();
400: if (xmlLogger.isLoggable(Level.FINER)) {
401: xmlLogger.finer("2 " + readresult);
402: }
403: if (readresult.indexOf("(none)") > -1) {
404: //no topics present
405: more = false;
406: }
407:
408: while (more) {
409: //adds new cat s to the menu
410: if (readresult.indexOf("topic") == -1) {
411: final String toAdd = readresult.substring(6);
412: menuContent.add(toAdd);
413: } else {
414: more = false;
415: }
416: //read </newcat
417: readresult = fromProc.readLine();
418: if (xmlLogger.isLoggable(Level.FINER))
419: xmlLogger.finer("2 " + readresult);
420: //read <newcat (normally)
421: readresult = fromProc.readLine();
422: if (xmlLogger.isLoggable(Level.FINER))
423: xmlLogger.finer("3 " + readresult);
424: if (readresult.indexOf("topic") != -1) {
425: //no more categories
426: more = false;
427: }
428: //read next cat / topic
429: readresult = fromProc.readLine();
430: if (xmlLogger.isLoggable(Level.FINER))
431: xmlLogger.finer("4 " + readresult);
432: }
433: //set topic
434: grammarName = readresult.substring(4) + " ";
435: //read </topic>
436: readresult = fromProc.readLine();
437: if (xmlLogger.isLoggable(Level.FINER))
438: xmlLogger.finer("2 " + readresult);
439: //read <language>
440: readresult = fromProc.readLine();
441: if (xmlLogger.isLoggable(Level.FINER))
442: xmlLogger.finer("3 " + readresult);
443: //read actual language
444: readresult = fromProc.readLine();
445: if (xmlLogger.isLoggable(Level.FINER))
446: xmlLogger.finer("4 " + readresult);
447:
448: //read the languages and select the last non-abstract
449: more = true;
450: while (more) {
451: if ((readresult.indexOf("/gfinit") == -1)
452: && (readresult.indexOf("lin") == -1)) {
453: //form lang and Menu menu:
454: final String langName = readresult.substring(4);
455: languages.add(langName);
456: } else {
457: more = false;
458: }
459: // read </language>
460: readresult = fromProc.readLine();
461: if (xmlLogger.isLoggable(Level.FINER))
462: xmlLogger.finer("2 " + readresult);
463: // read <language> or </gfinit...>
464: readresult = fromProc.readLine();
465: if (xmlLogger.isLoggable(Level.FINER))
466: xmlLogger.finer("3 " + readresult);
467: if ((readresult.indexOf("/gfinit") != -1)
468: || (readresult.indexOf("lin") != -1)) {
469: more = false;
470: }
471: // registering the file name:
472: if (readresult.indexOf("language") != -1) {
473: String path = readresult.substring(readresult
474: .indexOf('=') + 1, readresult.indexOf('>'));
475: path = path.substring(path
476: .lastIndexOf(File.separatorChar) + 1);
477: if (xmlLogger.isLoggable(Level.FINE))
478: xmlLogger.fine("language: " + path);
479: paths.add(path);
480: }
481: // in case of finished, read the final "" after </gfinit>,
482: // otherwise the name of the next language
483: readresult = fromProc.readLine();
484: if (xmlLogger.isLoggable(Level.FINER))
485: xmlLogger.finer("4 " + readresult);
486: }
487: } catch (IOException e) {
488: xmlLogger.warning(e.getMessage());
489: }
490: String[] menuContentArray = Utils.vector2Array(menuContent);
491: String[] languagesArray = Utils.vector2Array(languages);
492: String[] pathsArray = Utils.vector2Array(paths);
493: NewCategoryMenuResult result = new NewCategoryMenuResult(
494: grammarName, menuContentArray, languagesArray,
495: pathsArray);
496: return result;
497: }
498:
499: /**
500: * Reads the hmsg part of the XML that is put out from GF.
501: * Everything in [] given in front of a GF command will be rewritten here.
502: * This method does nothing when no hmsg part is present.
503: *
504: * If a '$' appears in this string, everything that comes after it
505: * will be in result.second.
506: * ;; and [] don't work in the [] for the hmsg,
507: * therfore the following replacements are done:
508: * %% for ;;
509: * ( for [
510: * ) for ]
511: *
512: * If one of the characters c,t,n comes before, the following is done:
513: * c The output will be cleared before the linearization (TODO: done anyway?)
514: * t The treeChanged flag will be set to true
515: * n The newObject flag will be set to true
516: * p No other probing run should be done (avoid cycles)
517: * r To prevent the execution of automatically triggered commands to prevent recursion
518: *
519: * @param prevreadresult The last line read from GF
520: * @return first: the last line this method has read;
521: * second: the string after $, null if that is not present
522: */
523: protected Hmsg readHmsg(String prevreadresult) {
524: if ((prevreadresult != null)
525: && (prevreadresult.indexOf("<hmsg>") > -1)) {
526: StringBuffer s = new StringBuffer("");
527: String commandAppendix = null;
528: try {
529: boolean onceAgain = true;
530: boolean recurse = true;
531: boolean newObj = false;
532: boolean treeCh = false;
533: boolean clear = false;
534: String readresult = fromProc.readLine();
535: if (xmlLogger.isLoggable(Level.FINER))
536: xmlLogger.finer("7 " + readresult);
537: while (readresult.indexOf("/hmsg") == -1) {
538: s.append(readresult).append('\n');
539: readresult = fromProc.readLine();
540: if (xmlLogger.isLoggable(Level.FINER))
541: xmlLogger.finer("7 " + readresult);
542: }
543: int commandAppendixStart = s.indexOf("$");
544: if (commandAppendixStart > -1
545: && commandAppendixStart < s.length() - 1) { //present, but not the last character
546: commandAppendix = s.substring(
547: commandAppendixStart + 1, s.indexOf("\n")); //two \n trail the hmsg
548: //;; and [] don't work in the [] for the hmsg
549: commandAppendix = Utils.replaceAll(commandAppendix,
550: "%%", ";;");
551: commandAppendix = Utils.replaceAll(commandAppendix,
552: "(", "[");
553: commandAppendix = Utils.replaceAll(commandAppendix,
554: ")", "]");
555: } else {
556: commandAppendixStart = s.length();
557: }
558: if (s.indexOf("c") > -1
559: && s.indexOf("c") < commandAppendixStart) {
560: //clear output before linearization
561: clear = true;
562: }
563: if (s.indexOf("t") > -1
564: && s.indexOf("t") < commandAppendixStart) {
565: //tree has changed
566: treeCh = true;
567: }
568: if (s.indexOf("p") > -1
569: && s.indexOf("p") < commandAppendixStart) {
570: //we must not probe again
571: onceAgain = false;
572: }
573: if (s.indexOf("r") > -1
574: && s.indexOf("r") < commandAppendixStart) {
575: //we must not probe again
576: recurse = false;
577: }
578:
579: if (s.indexOf("n") > -1
580: && s.indexOf("n") < commandAppendixStart) {
581: //a new object has been created
582: newObj = true;
583: }
584: if (logger.isLoggable(Level.FINE)) {
585: if (commandAppendix != null) {
586: logger.fine("command appendix read: '"
587: + commandAppendix + "'");
588: }
589: }
590: return new Hmsg(readresult, commandAppendix, onceAgain,
591: recurse, newObj, treeCh, clear);
592: } catch (IOException e) {
593: System.err.println(e.getMessage());
594: e.printStackTrace();
595: return new Hmsg("", null, false, true, false, true,
596: false);
597: }
598: } else {
599: return new Hmsg(prevreadresult, null, true, true, false,
600: true, false);
601: }
602: }
603:
604: /**
605: * Parses the GF-output between <menu> and </menu> tags
606: * and puts a StringTuple for each show/send pair into the
607: * return vector.
608: * @return A Vector of StringTuple as described above
609: */
610: protected Vector readRefinementMenu() {
611: if (xmlLogger.isLoggable(Level.FINER))
612: xmlLogger.finer("list model changing! ");
613: String s = "";
614: Vector printnameVector = new Vector();
615: Vector commandVector = new Vector();
616: Vector gfCommandVector = new Vector();
617: try {
618: //read <menu>
619: String readresult = fromProc.readLine();
620: if (xmlLogger.isLoggable(Level.FINER))
621: xmlLogger.finer("7 " + readresult);
622: //read item
623: readresult = fromProc.readLine();
624: if (xmlLogger.isLoggable(Level.FINER))
625: xmlLogger.finer("8 " + readresult);
626: while (readresult.indexOf("/menu") == -1) {
627: //read show
628: readresult = fromProc.readLine();
629: if (xmlLogger.isLoggable(Level.FINER))
630: xmlLogger.finer("8 " + readresult);
631: while (readresult.indexOf("/show") == -1) {
632: readresult = fromProc.readLine();
633: if (xmlLogger.isLoggable(Level.FINER))
634: xmlLogger.finer("9 " + readresult);
635: if (readresult.indexOf("/show") == -1) {
636: if (readresult.length() > 8)
637: s += readresult.trim();
638: else
639: s += readresult;
640: }
641: }
642: // if (s.charAt(0)!='d')
643: // listModel.addElement("Refine " + s);
644: // else
645: String showText = s;
646: printnameVector.addElement(s);
647: s = "";
648: //read /show
649: //read send
650: readresult = fromProc.readLine();
651: if (xmlLogger.isLoggable(Level.FINER))
652: xmlLogger.finer("8 " + readresult);
653: readresult = fromProc.readLine();
654: if (xmlLogger.isLoggable(Level.FINER))
655: xmlLogger.finer("8 " + readresult);
656: String myCommand = readresult;
657: commandVector.add(readresult);
658: //read /send (discarded)
659: readresult = fromProc.readLine();
660: if (xmlLogger.isLoggable(Level.FINER))
661: xmlLogger.finer("8 " + readresult);
662:
663: // read /item
664: readresult = fromProc.readLine();
665: if (xmlLogger.isLoggable(Level.FINER))
666: xmlLogger.finer("8 " + readresult);
667: readresult = fromProc.readLine();
668: if (xmlLogger.isLoggable(Level.FINER))
669: xmlLogger.finer("8 " + readresult);
670:
671: StringTuple st = new StringTuple(myCommand.trim(),
672: showText);
673: gfCommandVector.addElement(st);
674: }
675: } catch (IOException e) {
676: System.err.println(e.getMessage());
677: e.printStackTrace();
678: }
679: return gfCommandVector;
680: }
681:
682: /**
683: * Reads the output from GF until the ending tag corresponding to the
684: * given opening tag is read.
685: * @param opening tag in the format of >gfinit<
686: */
687: protected void skipChild(String opening) {
688: String closing = (new StringBuffer(opening)).insert(1, '/')
689: .toString();
690: try {
691: String nextRead = fromProc.readLine();
692: if (logger.isLoggable(Level.FINER)) {
693: logger.finer("3 " + nextRead);
694: }
695: while (!nextRead.trim().equals(closing)) {
696: nextRead = fromProc.readLine();
697: if (logger.isLoggable(Level.FINER)) {
698: logger.finer("3 " + nextRead);
699: }
700: }
701: } catch (IOException e) {
702: System.err
703: .println("Could not read from external process:\n"
704: + e);
705: }
706: }
707: }
|