001: /*
002: * This file or a portion of this file is licensed under the terms of
003: * the Globus Toolkit Public License, found in file ../GTPL, or at
004: * http://www.globus.org/toolkit/download/license.html. This notice must
005: * appear in redistributions of this file, with or without modification.
006: *
007: * Redistributions of this Software, with or without modification, must
008: * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
009: * some other similar material which is provided with the Software (if
010: * any).
011: *
012: * Copyright 1999-2004 University of Chicago and The University of
013: * Southern California. All rights reserved.
014: */
015:
016: package org.griphyn.vdl.dax;
017:
018: import org.griphyn.common.util.Currently;
019: import org.griphyn.vdl.dax.*;
020: import org.griphyn.vdl.classes.LFN;
021: import org.griphyn.vdl.classes.Derivation;
022: import java.util.*;
023: import java.io.Writer;
024: import java.io.IOException;
025:
026: /**
027: * This class is the container for an abstract DAG description. It consists
028: * of three parts.<p>
029: *
030: * <ol>
031: * <li> {@link Filename} deals with the filenames that are used in the
032: * picture of the DAG - does a file go into the DAG, come out of the
033: * DAG, or is it an intermediary file. There are multiple instances
034: * stored in a DAX.
035: *
036: * <li> {@link Job} deals with the description of all jobs in a DAG.
037: * Each job has a logical transformation, commandline argument, possible
038: * stdio redirection, and a potential set of <code>Profile</code>
039: * settings. There are multiple instance stored in a DAX.
040: *
041: * <li> {@link Child} deals with the dependency in a two-level fashion.
042: * It contains a list of child to parent(s) relationships. The children
043: * and parents refer to jobs from the previous section. There are
044: * multiple instances stored in a DAX.
045: * </ol>
046: *
047: * @author Jens-S. Vöckler
048: * @author Yong Zhao
049: * @version $Revision: 366 $
050: */
051: public class ADAG extends DAX implements Cloneable {
052: /**
053: * The "official" namespace URI of the DAX schema.
054: */
055: public static final String SCHEMA_NAMESPACE = "http://pegasus.isi.edu/schema/DAX";
056:
057: /**
058: * The "not-so-official" location URL of the DAX schema definition.
059: */
060: public static final String SCHEMA_LOCATION = "http://pegasus.isi.edu/schema/dax-2.1.xsd";
061:
062: /**
063: * The version to report.
064: */
065: public static final String SCHEMA_VERSION = "2.1";
066:
067: /**
068: * list of all filenames in terms of Filename
069: * @see Filename
070: */
071: private TreeMap m_fileMap;
072:
073: /**
074: * list of all jobs
075: * @see Job
076: */
077: private TreeMap m_jobMap;
078:
079: /**
080: * list of all child nodes to construct DAG.
081: * @see Child
082: */
083: private TreeMap m_childMap;
084:
085: /**
086: * list of replacements for node collapsion from compounds.
087: */
088: private TreeMap m_replace;
089: private boolean m_dirty;
090:
091: /**
092: * optional name of this document.
093: */
094: private String m_name = null;
095:
096: /**
097: * When generating alternatives, this is the total number of alternatives.
098: */
099: private int m_size;
100:
101: /**
102: * When generating alternatives, this is the zero-based count.
103: */
104: private int m_index;
105:
106: /**
107: * The version to report back, or alternatively to the version that
108: * the DAX file had when reading.
109: */
110: private String m_version = SCHEMA_VERSION;
111:
112: /**
113: * Creates and returns a copy of this object.
114: * @return a new instance, deep copy of elements
115: */
116: public Object clone() {
117: ADAG result = new ADAG(this .m_size, this .m_index, this .m_name);
118:
119: // maybe unsafe?
120: result.setVersion(this .m_version);
121:
122: for (Iterator i = this .m_fileMap.values().iterator(); i
123: .hasNext();) {
124: result
125: .addFilename((Filename) ((Filename) i.next())
126: .clone());
127: }
128: for (Iterator i = this .m_jobMap.values().iterator(); i
129: .hasNext();) {
130: result.addJob((Job) ((Job) i.next()).clone());
131: }
132: for (Iterator i = this .m_childMap.values().iterator(); i
133: .hasNext();) {
134: result.addChild((Child) ((Child) i.next()).clone());
135: }
136: for (Iterator i = this .m_replace.keySet().iterator(); i
137: .hasNext();) {
138: String key = (String) i.next();
139: result.replaceParent(key, (String) this .m_replace.get(key));
140: }
141:
142: return result;
143: }
144:
145: /**
146: * Default ctor: construct a hollow shell to add data later.
147: */
148: public ADAG() {
149: m_size = 1;
150: m_index = 0;
151: m_jobMap = new TreeMap();
152: m_fileMap = new TreeMap();
153: m_childMap = new TreeMap();
154: m_replace = new TreeMap();
155: }
156:
157: /**
158: * Ctor: Construct a hollow shell with the required arguments.
159: *
160: * @param size is the total number of DAXes that will be constructed.
161: * @param index is the zero-based number in the total number of DAXes.
162: */
163: public ADAG(int size, int index) {
164: m_size = size;
165: m_index = index;
166: m_jobMap = new TreeMap();
167: m_fileMap = new TreeMap();
168: m_childMap = new TreeMap();
169: m_replace = new TreeMap();
170: }
171:
172: /**
173: * Ctor: Construct a hollow shell with all element attributes
174: *
175: * @param size is the total number of DAXes that will be constructed.
176: * @param index is the zero-based number in the total number of DAXes.
177: * @param name is an optional name to use for the DAX. In later versions
178: * this might be useful, if several DAXes are interleaved on the same
179: * connection.
180: */
181: public ADAG(int size, int index, String name) {
182: m_name = name;
183: m_size = size;
184: m_index = index;
185: m_jobMap = new TreeMap();
186: m_fileMap = new TreeMap();
187: m_childMap = new TreeMap();
188: m_replace = new TreeMap();
189: }
190:
191: /**
192: * Adds a logical filename string with input or output notion to the
193: * list of maintained filenames. If the filename does not exist
194: * previously, a new entry is added. If the filename does exist, the
195: * io state will be checked. A filename that was previously an input,
196: * and is now an output, will become inout. If a filename was ever
197: * declared not-transfer or not-register, it will maintain these
198: * attributes. Each filename is only added once.
199: *
200: * @param lfn is the logical filename string
201: * @param isInput is a predicate with true to signal an input filename.
202: * @param temporary is a temp file hint, currently unused.
203: * @param dontRegister a true value will be propagated (mono-flop)
204: * @param dontTransfer any non-mandatory value will be propagated
205: */
206: public void addFilename(String lfn, boolean isInput,
207: String temporary, boolean dontRegister, int dontTransfer) {
208: Filename f = (Filename) this .m_fileMap.get(lfn);
209: if (f != null) {
210: // found! check link status
211: if ((f.getLink() == LFN.INPUT && !isInput)
212: || (f.getLink() == LFN.OUTPUT && isInput)) {
213: // need to change linkage
214: f.setLink(LFN.INOUT);
215: }
216: // set file hint
217: if (temporary != null)
218: f.setTemporary(temporary);
219: if (dontRegister)
220: f.setRegister(!dontRegister);
221: if (dontTransfer != LFN.XFER_MANDATORY)
222: f.setTransfer(dontTransfer);
223: } else {
224: // file is not in list, add it
225: // PS: and it is most likely not a stdio filename?
226: this .m_fileMap.put(lfn, new Filename(lfn,
227: isInput ? LFN.INPUT : LFN.OUTPUT, temporary,
228: dontRegister, dontTransfer, null));
229: }
230: }
231:
232: /**
233: * Adds a completely constructed {@link Filename} structure to the
234: * map of filenames. The structure must be assembled outside. This
235: * method is primarily a convenience for the {@link #clone()} method.
236: *
237: * @param lfn is the Filename instance.
238: * @return true, if the bag did not contain an identical Filename already.
239: */
240: protected boolean addFilename(Filename lfn) {
241: String id = lfn.getFilename();
242: boolean result = !this .m_fileMap.containsKey(id);
243: this .m_fileMap.put(id, lfn);
244: return result;
245: }
246:
247: /**
248: * Adds a completely constructed {@link Job} structure to the
249: * map of jobs. The structure must be assembled outside, using
250: * the related classes. The job ID will be taken as unique key.
251: * If a job with this ID already exists in the DAX, it will be
252: * replaced with the new job.
253: *
254: * @param job is the new job to add
255: * @return true, if the bag did not contain this job already.
256: */
257: public boolean addJob(Job job) {
258: String id = job.getID();
259: boolean result = !this .m_jobMap.containsKey(id);
260: this .m_jobMap.put(id, job);
261: return result;
262: }
263:
264: /**
265: * Adds a child node which was constructed elsewhere to the list of
266: * known children. If the child already exists, nothing is done.
267: *
268: * @param child is the new {@link Child} instance to put into the bag.
269: * @return true if the bag did not already contain the specified element.
270: */
271: public boolean addChild(Child child) {
272: if (this .m_childMap.containsKey(child.getChild())) {
273: return false;
274: } else {
275: this .m_childMap.put(child.getChild(), child);
276: return true;
277: }
278: }
279:
280: /**
281: * Adds a child node without any parent relationship to the list of
282: * known children. If the child already exists, nothing is done.
283: *
284: * @param child_id is the new child to put into the bag.
285: * @return true if the bag did not already contain the specified element.
286: */
287: public boolean addChild(String child_id) {
288: if (this .m_childMap.containsKey(child_id)) {
289: return false;
290: } else {
291: this .m_childMap.put(child_id, new Child(child_id));
292: return true;
293: }
294: }
295:
296: /**
297: * Adds a child node with a parent relationship to the list of known
298: * children. If the child already exists, but the parent relationship
299: * is not known, it will be added to the child's list of parents. If
300: * the child already exists and the parent relationship is known,
301: * nothing is done.
302: *
303: * @param child_id is the id of the child for which to modify a parent
304: * @param parent_id is the new parent to add to the specified child.
305: * @return true if the bag did not already contain the relationship. */
306: public boolean addChild(String child_id, String parent_id) {
307: Child current = (Child) this .m_childMap.get(child_id);
308: if (current == null) {
309: // unknown child, add to bag
310: this .m_childMap.put(child_id,
311: new Child(child_id, parent_id));
312: return true;
313: } else {
314: // child is know, check the parent
315: if (!current.getParent(parent_id)) {
316: // parent is unknown, add to child
317: current.addParent(parent_id);
318: return true;
319: } else {
320: // parent is already known
321: return false;
322: }
323: }
324: }
325:
326: /**
327: * Registers a job node collapsion as a replacement.
328: */
329: public String replaceParent(String oldid, String newid) {
330: String old = (String) this .m_replace.put(oldid, newid);
331: this .m_dirty = true;
332: return old;
333: }
334:
335: /**
336: * Accessor: Provides an iterator for the bag of filenames.
337: * @return the iterator for <code>Filename</code> elements.
338: * @see Filename
339: * @deprecated Use the new Collection based interfaces
340: */
341: public Enumeration enumerateFilename() {
342: return Collections.enumeration(this .m_fileMap.values());
343: }
344:
345: /**
346: * Accessor: Provides an iterator for the bag of jobs.
347: * @return the iterator for <code>Job</code> elements.
348: * @see Job
349: * @deprecated Use the new Collection based interfaces
350: */
351: public Enumeration enumerateJob() {
352: return Collections.enumeration(this .m_jobMap.values());
353: }
354:
355: /**
356: * Accessor: Provides an iterator for the bag of relationships.
357: * @return the iterator for <code>Child</code> elements.
358: * @see Child
359: * @deprecated Use the new Collection based interfaces
360: */
361: public Enumeration enumerateChild() {
362: if (this .m_dirty)
363: updateChildren();
364: return Collections.enumeration(this .m_childMap.values());
365: }
366:
367: /**
368: * Accessor: Obtains a <code>Filename</code> from its string.
369: *
370: * @param lfn is the logical filename string to look it up with.
371: * @return the filename instance at the specified place.
372: * @see #addFilename( Filename )
373: * @see #setFilename( Filename )
374: */
375: public Filename getFilename(String lfn) {
376: return (Filename) this .m_fileMap.get(lfn);
377: }
378:
379: /**
380: * Accessor: Obtains the index of filename instances.
381: *
382: * @return the number of arguments in the filename list.
383: * @see Filename
384: */
385: public int getFilenameCount() {
386: return this .m_fileMap.size();
387: }
388:
389: /**
390: * Accessor: Counts the number of jobs in the abstract DAG.
391: *
392: * @return the number of jobs.
393: */
394: public int getJobCount() {
395: return this .m_jobMap.size();
396: }
397:
398: /**
399: * Access: Counts the number of dependencies in the DAG.
400: *
401: * @return dependency count, which may be zilch.
402: */
403: public int getChildCount() {
404: if (this .m_dirty)
405: updateChildren();
406: return this .m_childMap.size();
407: }
408:
409: /**
410: * Accessor: Obtains the zero-based index.
411: *
412: * @return a number in the interval [0,size-1].
413: * @see #setIndex( int )
414: */
415: public int getIndex() {
416: return this .m_index;
417: }
418:
419: /**
420: * Accessor: Obtains a job by its id from the job list.
421: *
422: * @return a job or null, if not found.
423: * @see #addJob( Job )
424: */
425: public Job getJob(String jobID) {
426: return (Job) this .m_jobMap.get(jobID);
427: }
428:
429: /**
430: * Accessor: Obtains the name of the DAX.
431: *
432: * @return the name of this DAX, or <code>null</code>, if no name
433: * was specified.
434: * @see #setName( String )
435: */
436: public String getName() {
437: return this .m_name;
438: }
439:
440: /**
441: * Accessor: Obtains the total number of alternatives. This is the
442: * number of DAXes generatable from alternatives.
443: * @return a positive natural integer.
444: * @see #setSize( int )
445: */
446: public int getSize() {
447: return this .m_size;
448: }
449:
450: /**
451: * Accessor: Obtains the version that will be reported in the DAX.
452: * @return the version as a string.
453: * @see #setVersion( String )
454: * @since 1.7
455: */
456: public String getVersion() {
457: return this .m_version;
458: }
459:
460: /**
461: * Accessor: Provides an iterator for the bag of filenames.
462: * @return the iterator for <code>Filename</code> elements.
463: * @see Filename
464: */
465: public Iterator iterateFilename() {
466: return this .m_fileMap.values().iterator();
467: }
468:
469: /**
470: * Accessor: Provides an iterator for the bag of jobs.
471: * @return the iterator for <code>Job</code> elements.
472: * @see Job
473: */
474: public Iterator iterateJob() {
475: return this .m_jobMap.values().iterator();
476: }
477:
478: /**
479: * Accessor: Provides an iterator for the bag of relationships.
480: * @return the iterator for <code>Child</code> elements.
481: * @see Child
482: */
483: public Iterator iterateChild() {
484: if (this .m_dirty)
485: updateChildren();
486: return this .m_childMap.values().iterator();
487: }
488:
489: /**
490: * Accessor: Removes all filename instances.
491: * @see Filename
492: */
493: public void removeAllFilename() {
494: this .m_fileMap.clear();
495: }
496:
497: /**
498: * Accessor: Removes a specific logical filename instance from the bag.
499: *
500: * @param lfn is the logical filename string to refer to a filename.
501: * @return the {@link Filename} instance to which this lfn had been mapped
502: * in this hashtable, or <code>null</code> if the lfn did not have a mapping.
503: */
504: public Filename removeFilename(String lfn) {
505: return (Filename) this .m_fileMap.remove(lfn);
506: }
507:
508: /**
509: * Accessor: Overwrites an filename instance with a new one.
510: *
511: * @param vFilename is the new filename instance, which contains all
512: * necessary information.
513: */
514: public void setFilename(Filename vFilename) {
515: this .m_fileMap.put(vFilename.getFilename(), vFilename);
516: }
517:
518: /**
519: * Accessor: Replace this filename instance list with a new list.
520: *
521: * @param fileArray is the new list of Filename instances
522: * @see Filename
523: * @deprecated Use the new Collection based interfaces
524: */
525: public void setFilename(Filename[] fileArray) {
526: this .m_fileMap.clear();
527: for (int i = 0; i < fileArray.length; i++) {
528: this .m_fileMap
529: .put(fileArray[i].getFilename(), fileArray[i]);
530: }
531: }
532:
533: /**
534: * Accessor: Replace this filename instance list with a new list.
535: *
536: * @param files is the new collection of Filename instances
537: * @see Filename
538: */
539: public void setFilename(java.util.Collection files) {
540: this .m_fileMap.clear();
541: for (Iterator i = files.iterator(); i.hasNext();) {
542: Filename lfn = (Filename) i.next();
543: this .m_fileMap.put(lfn.getFilename(), lfn);
544: }
545: }
546:
547: /**
548: * Accessor: Replace this filename instance list with a map.
549: *
550: * @param files is the new map of Filename instances
551: * @see Filename
552: */
553: public void setFilename(java.util.Map files) {
554: this .m_fileMap.clear();
555: this .m_fileMap.putAll(files);
556: }
557:
558: /**
559: * Acessor: Sets a new zero-based index for this document. The index
560: * is used in conjunction with the total number of documents count.
561: *
562: * @param index is the new zero-based index of this element.
563: * @see #getIndex()
564: */
565: public void setIndex(int index) {
566: this .m_index = index;
567: }
568:
569: /**
570: * Acessor: Sets a new optional name for this document.
571: *
572: * @param name is the new name.
573: * @see #getName()
574: */
575: public void setName(String name) {
576: this .m_name = name;
577: }
578:
579: /**
580: * Acessor: Sets a new total document count in this document. The count
581: * is used in conjunction with the zero-based document index.
582: *
583: * @param size is the new total document count.
584: * @see #getSize()
585: */
586: public void setSize(int size) {
587: this .m_size = size;
588: }
589:
590: /**
591: * Acessor: Sets a new version number for this document. The version
592: * number is taken by the abstract planner to support a range of valid
593: * DAX documents.
594: *
595: * @param version is the new version number as string composed of two
596: * integers separted by a period.
597: * @see #getVersion()
598: * @since 1.7
599: */
600: public void setVersion(String version) {
601: this .m_version = version;
602: }
603:
604: private void updateChildren() {
605: // find all child nodes in need of replacement
606: TreeMap temp = new TreeMap();
607: for (Iterator i = this .m_childMap.values().iterator(); i
608: .hasNext();) {
609: Child newchild = ((Child) i.next())
610: .updateChild(this .m_replace);
611: if (temp.containsKey(newchild.getChild())) {
612: // need to merge two definitions
613: Child oldchild = (Child) temp.get(newchild.getChild());
614: for (Iterator j = oldchild.iterateParent(); j.hasNext();) {
615: newchild.addParent((String) j.next());
616: }
617: }
618: // plain insertion
619: temp.put(newchild.getChild(), newchild);
620: }
621: this .m_childMap = temp;
622: this .m_dirty = false;
623: }
624:
625: /**
626: * Adjusts all job levels along the search path. Given a starting point,
627: * this method will re-iterate the search-tree, and adjust the level of
628: * each known job by the specified distance.
629: *
630: * @param id is the job id to start
631: * @param distance is the increment (or decrement for negative).
632: * @return number of jobs adjusted?
633: */
634: public int adjustLevels(String id, int distance) {
635: int result = 0;
636:
637: if (m_jobMap.containsKey(id)) {
638: Job job = (Job) m_jobMap.get(id);
639: job.setLevel(job.getLevel() + distance);
640: result++;
641:
642: // also recursively adjust all known parents of this job
643: if (m_childMap.containsKey(id)) {
644: Child c = (Child) m_childMap.get(id);
645: for (Iterator i = c.iterateParent(); i.hasNext();) {
646: result += adjustLevels((String) i.next(), distance);
647: }
648: }
649: }
650:
651: // done
652: return result;
653: }
654:
655: /**
656: * Converts the active state into something meant for human consumption.
657: * The method will be called when recursively traversing the instance
658: * tree.
659: *
660: * @param stream is a stream opened and ready for writing. This can also
661: * be a string stream for efficient output.
662: */
663: public void toString(Writer stream) throws IOException {
664: String newline = System.getProperty("line.separator", "\r\n");
665:
666: // FIXME: default name of a DAX w/o name is "test"
667: String daxname = this .m_name != null ? this .m_name : "test";
668: stream.write("adag ");
669: stream.write(escape(daxname));
670: stream.write(" {");
671: stream.write(newline);
672:
673: stream.write(" count=");
674: stream.write((new Integer(this .m_size)).toString());
675: stream.write(';');
676: stream.write(newline);
677:
678: stream.write(" index=");
679: stream.write((new Integer(this .m_index)).toString());
680: stream.write(';');
681: stream.write(newline);
682:
683: // part 1: filelist
684: stream.write(" files {");
685: stream.write(newline);
686: for (Iterator i = this .m_fileMap.values().iterator(); i
687: .hasNext();) {
688: stream.write(" ");
689: ((Filename) i.next()).toString(stream);
690: stream.write(newline);
691: }
692: stream.write(" }");
693: stream.write(newline);
694:
695: // part 2: job list
696: stream.write(" jobs {");
697: stream.write(newline);
698: for (Iterator i = this .m_jobMap.values().iterator(); i
699: .hasNext();) {
700: ((Job) i.next()).toString(stream);
701: }
702: stream.write(" }");
703: stream.write(newline);
704:
705: // part 3: dependencies
706: stream.write(" dependencies {");
707: stream.write(newline);
708: if (this .m_dirty)
709: updateChildren();
710: for (Iterator i = this .m_childMap.values().iterator(); i
711: .hasNext();) {
712: ((Child) i.next()).toString(stream);
713: }
714: stream.write(" }");
715: stream.write(newline);
716:
717: stream.write('}');
718: stream.write(newline);
719: stream.flush();
720: }
721:
722: /**
723: * Writes the header of the XML output. The output contains the special
724: * strings to start an XML document, some comments, and the root element.
725: * The latter points to the XML schema via XML Instances.
726: *
727: * @param stream is a stream opened and ready for writing. This can also
728: * be a string stream for efficient output.
729: * @param indent is a <code>String</code> of spaces used for pretty
730: * printing. The initial amount of spaces should be an empty string.
731: * The parameter is used internally for the recursive traversal.
732: * @param namespace is the XML schema namespace prefix. If neither
733: * empty nor null, each element will be prefixed with this prefix,
734: * and the root element will map the XML namespace.
735: * @exception IOException if something fishy happens to the stream.
736: */
737: public void writeXMLHeader(Writer stream, String indent,
738: String namespace) throws IOException {
739: String newline = System.getProperty("line.separator", "\r\n");
740:
741: // FIXME: default name of a DAX w/o name is "test"
742: String daxname = this .m_name != null ? this .m_name : "test";
743:
744: // intro
745: if (indent != null && indent.length() > 0)
746: stream.write(indent);
747: stream.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
748: stream.write(newline);
749:
750: // when was this document generated
751: if (indent != null && indent.length() > 0)
752: stream.write(indent);
753: stream.write("<!-- generated: ");
754: stream.write(Currently.iso8601(false));
755: stream.write(" -->");
756: stream.write(newline);
757:
758: // who generated this document
759: if (indent != null && indent.length() > 0)
760: stream.write(indent);
761: stream.write("<!-- generated by: ");
762: stream.write(System.getProperties().getProperty("user.name",
763: "unknown"));
764: stream.write(" [");
765: stream.write(System.getProperties().getProperty("user.region",
766: "??"));
767: stream.write("] -->");
768: stream.write(newline);
769:
770: // root element with elementary attributes
771: if (indent != null && indent.length() > 0)
772: stream.write(indent);
773: stream.write('<');
774: if (namespace != null && namespace.length() > 0) {
775: stream.write(namespace);
776: stream.write(':');
777: }
778: stream.write("adag xmlns");
779: if (namespace != null && namespace.length() > 0) {
780: stream.write(':');
781: stream.write(namespace);
782: }
783: stream.write("=\"");
784: stream.write(SCHEMA_NAMESPACE);
785: stream
786: .write("\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"");
787: stream.write(SCHEMA_NAMESPACE);
788: stream.write(' ');
789: stream.write(SCHEMA_LOCATION);
790: stream.write('"');
791: writeAttribute(stream, " version=\"", SCHEMA_VERSION);
792:
793: writeAttribute(stream, " count=\"", Integer
794: .toString(this .m_size));
795: writeAttribute(stream, " index=\"", Integer
796: .toString(this .m_index));
797: writeAttribute(stream, " name=\"", daxname);
798:
799: // added with dax-1.9
800: writeAttribute(stream, " jobCount=\"", Integer
801: .toString(this .m_jobMap.size()));
802: writeAttribute(stream, " fileCount=\"", Integer
803: .toString(this .m_fileMap.size()));
804: writeAttribute(stream, " childCount=\"", Integer
805: .toString(this .m_childMap.size()));
806:
807: stream.write('>');
808: if (indent != null)
809: stream.write(newline);
810: }
811:
812: /**
813: * Dump the state of the current element as XML output. This function
814: * traverses all sibling classes as necessary, and converts the data
815: * into pretty-printed XML output. The stream interface should be able
816: * to handle large output efficiently.
817: *
818: * @param stream is a stream opened and ready for writing. This can also
819: * be a string stream for efficient output.
820: * @param indent is a <code>String</code> of spaces used for pretty
821: * printing. The initial amount of spaces should be an empty string.
822: * The parameter is used internally for the recursive traversal.
823: * @param namespace is the XML schema namespace prefix. If neither
824: * empty nor null, each element will be prefixed with this prefix,
825: * and the root element will map the XML namespace.
826: * @exception IOException if something fishy happens to the stream.
827: */
828: public void toXML(Writer stream, String indent, String namespace)
829: throws IOException {
830: String newline = System.getProperty("line.separator", "\r\n");
831: String newindent = indent == null ? null : indent + " ";
832:
833: // write prefix
834: writeXMLHeader(stream, indent, namespace);
835:
836: // part 1: filelist
837: stream
838: .write("<!-- part 1: list of all referenced files (may be empty) -->");
839: if (indent != null)
840: stream.write(newline);
841:
842: for (Iterator i = this .m_fileMap.values().iterator(); i
843: .hasNext();) {
844: if (indent != null)
845: stream.write(newindent);
846: ((Filename) i.next()).shortXML(stream, newindent,
847: namespace, 0x03);
848: if (indent != null)
849: stream.write(newline);
850: }
851:
852: // part 2: job list
853: stream
854: .write("<!-- part 2: definition of all jobs (at least one) -->");
855: if (indent != null)
856: stream.write(newline);
857: for (Iterator i = this .m_jobMap.values().iterator(); i
858: .hasNext();) {
859: ((Job) i.next()).toXML(stream, newindent, namespace);
860: }
861:
862: // part 3: dependencies
863: if (this .m_dirty)
864: updateChildren();
865: stream
866: .write("<!-- part 3: list of control-flow dependencies (may be empty) -->");
867: if (indent != null)
868: stream.write(newline);
869: for (Iterator i = this .m_childMap.values().iterator(); i
870: .hasNext();) {
871: ((Child) i.next()).toXML(stream, newindent, namespace);
872: }
873:
874: // close tag
875: if (indent != null && indent.length() > 0)
876: stream.write(indent);
877: stream.write("</");
878: if (namespace != null && namespace.length() > 0) {
879: stream.write(namespace);
880: stream.write(':');
881: }
882: stream.write("adag>");
883: stream.write(newline);
884: stream.flush();
885: }
886: }
|