001: /*
002: * Jacareto Copyright (c) 2002-2005
003: * Applied Computer Science Research Group, Darmstadt University of
004: * Technology, Institute of Mathematics & Computer Science,
005: * Ludwigsburg University of Education, and Computer Based
006: * Learning Research Group, Aachen University. All rights reserved.
007: *
008: * Jacareto is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation; either
011: * version 2 of the License, or (at your option) any later version.
012: *
013: * Jacareto is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public
019: * License along with Jacareto; if not, write to the Free
020: * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
021: *
022: */
023:
024: package jacareto.struct;
025:
026: import jacareto.convert.SelectionConverter;
027: import jacareto.record.MediaClipRecordable;
028: import jacareto.record.PositionRecordable;
029: import jacareto.record.ReadAccessRecord;
030: import jacareto.record.RecordException;
031: import jacareto.record.Recordable;
032: import jacareto.record.UnknownRecordable;
033: import jacareto.record.VectorRecord;
034: import jacareto.struct.event.StructureChangeEvent;
035: import jacareto.system.Environment;
036: import jacareto.toolkit.EnhancedHashtable;
037:
038: import org.jdom.Document;
039: import org.jdom.Element;
040: import org.jdom.IllegalDataException;
041: import org.jdom.IllegalNameException;
042: import org.jdom.JDOMException;
043: import org.jdom.input.SAXBuilder;
044: import org.jdom.output.XMLOutputter;
045:
046: import java.io.File;
047: import java.io.FileOutputStream;
048: import java.io.IOException;
049:
050: import java.util.Enumeration;
051: import java.util.Hashtable;
052: import java.util.Iterator;
053: import java.util.Vector;
054:
055: /**
056: * This class stores structure elements to a xml file.
057: *
058: * @author <a href="mailto:markus.bois@web.de">Markus Bois</a>
059: * @version 1.02
060: */
061: public class XMLStructure extends ExternalStructure {
062: /** Load the converters from the customization at the beginning. */
063: public static final int INIT_CUSTOM = 0;
064:
065: /** There should no converters be added at the beginning. */
066: public static final int INIT_EMPTY = 1;
067:
068: /** The default root name. */
069: private static final String defaultRoot = "JacaretoStructure";
070:
071: /** The structure root name. */
072: private static final String structureRoot = "Structure";
073:
074: /** The record root name. */
075: private static final String recordRoot = "Record";
076:
077: /** The prefix for the customization. */
078: private String customizationPrefix;
079:
080: /** The selection converter for records. */
081: private SelectionConverter selectionRecordConverter;
082:
083: /** The selection converter for structure elements. */
084: private SelectionConverter selectionStructureConverter;
085:
086: /** The filename of the structure file. */
087: private String filename;
088:
089: /** The root element. */
090: private Element rootElement;
091:
092: /** The root element for records */
093: private Element rootRecord;
094:
095: /** The root element for structre elements */
096: private Element rootStructure;
097:
098: /** The recordables of the file */
099: private Recordable[] record;
100:
101: /** The root elemt of the file */
102: private StructureElement root;
103:
104: /** Is the structure opened or not? */
105: private boolean isOpen;
106:
107: /** Whether the structure should be saved when closing or not. */
108: private boolean saveBeforeClosing;
109:
110: /** The class loader of the session */
111: private ClassLoader classLoader;
112:
113: /** The orginal setted record (only available when setRecord is called */
114: private ReadAccessRecord systemRecord;
115:
116: /**
117: * Creates a new XMLStructure with the specified filename. The filename will be extended
118: * automatically if it does not end with ".xml" The structure will be stored to the
119: * xml file before it will be closed.
120: *
121: * @param env the environment
122: * @param filename The filename of the structurefile
123: * @param customizationPrefix the prefix of the customization for this xml structure
124: * @param init {@link #INIT_CUSTOM} or {@link #INIT_EMPTY}
125: * @param classLoader the class loader
126: */
127: public XMLStructure(Environment env, String filename,
128: String customizationPrefix, int init,
129: ClassLoader classLoader) {
130: super (env);
131: this .filename = filename;
132: this .isOpen = false;
133: this .customizationPrefix = customizationPrefix;
134: this .classLoader = classLoader;
135:
136: // extension of the filename
137: if (!this .filename.endsWith(".xml")) {
138: this .filename += ".xml";
139: }
140:
141: String nameRecordConverters = getCustomization().getString(
142: customizationPrefix + ".RecordConverters", "XMLRecord");
143:
144: selectionRecordConverter = new SelectionConverter(env,
145: nameRecordConverters + ".Converters", init, classLoader);
146: selectionStructureConverter = new SelectionConverter(env,
147: customizationPrefix + ".Converters", init, classLoader);
148:
149: setSaveBeforeClosing(true);
150: }
151:
152: /**
153: * Creates a new XMLStructure with the specified filename. The filename will be extended
154: * automatically if it does not end with ".xml" The stucture will be stored to the
155: * xml file before it will be closed. The default customization prefix is
156: * <code>XMLStructure</code>.
157: *
158: * @param env the environment
159: * @param filename The filename of the recordfile
160: * @param init {@link #INIT_CUSTOM} or {@link #INIT_EMPTY}
161: */
162: public XMLStructure(Environment env, String filename, int init) {
163: this (env, filename, "XMLStructure", init, null);
164: }
165:
166: /**
167: * Creates a new XMLStructure with the specified filename. The filename will be extended
168: * automatically if it does not end with ".xml" The structure will be stored to the
169: * xml file before it will be closed. The xml converters will be loaded by the given class
170: * loader. The default customization prefix is <code>XMLStructure</code>.
171: *
172: * @param env the environment
173: * @param filename The filename of the recordfile
174: * @param init {@link #INIT_CUSTOM} or {@link #INIT_EMPTY}
175: * @param classLoader the class loader
176: */
177: public XMLStructure(Environment env, String filename, int init,
178: ClassLoader classLoader) {
179: this (env, filename, "XMLStructure", init, classLoader);
180: }
181:
182: /**
183: * Makes this stucture get ready for adding elements. If the xml file exists, it will be opened
184: * and read. In this case all information recorded will be added to the existing record. If
185: * you want to overwrite the old information, you have to call the method {@link #clear()}
186: * before you start adding.
187: *
188: * @throws StructureException if the structure is already open, or if there are problems with
189: * xml parsing
190: */
191: public void open() throws StructureException {
192: if (!isOpen()) {
193: File file = new File(filename);
194:
195: if (file.exists()) {
196: try {
197: getLogger()
198: .debug(
199: getLanguage()
200: .getString(
201: "Structures.XMLStructure.Msg.LoadingFile")
202: + ": " + filename);
203:
204: SAXBuilder builder = new SAXBuilder();
205: Document xmlEventRecordDocument = builder
206: .build(file);
207:
208: rootElement = xmlEventRecordDocument
209: .getRootElement();
210:
211: if (rootElement == null) {
212: rootElement = new Element(getCustomization()
213: .getString(
214: customizationPrefix + ".Root",
215: defaultRoot));
216: }
217:
218: rootStructure = rootElement.getChild(structureRoot);
219: rootRecord = rootElement.getChild(recordRoot);
220:
221: if (rootStructure == null) {
222: rootStructure = new Element(structureRoot);
223: }
224:
225: if (rootRecord == null) {
226: rootRecord = new Element(recordRoot);
227: }
228:
229: createRecord();
230: createStructure();
231: } catch (JDOMException ex) {
232: throw new StructureException(
233: getLanguage()
234: .getString(
235: "Structures.XMLStructure.Error.Read")
236: + ": " + filename);
237: }
238: } else {
239: rootElement = new Element(getCustomization().getString(
240: customizationPrefix + ".Root", defaultRoot));
241: rootStructure = new Element(structureRoot);
242: rootRecord = new Element(recordRoot);
243: }
244:
245: isOpen = true;
246: fireStructureChange(new StructureChangeEvent(
247: StructureChangeEvent.STRUCTURE_OPENED, null));
248: } else {
249: throw new StructureException(getLanguage().getString(
250: "Structures.Structure.Error.AlreadyOpen"));
251: }
252: }
253:
254: /**
255: * Finishes adding.
256: *
257: * @throws StructureException if the structure is not open, or if there are problems with
258: * writing the xml file TODO: von Oliver Specht: Wieso heisst diese Methode close(),
259: * wenn sie schreibt??? Anscheinend ist das ja die einzige Methode, die XML schreibt,
260: * also wieso nicht einfach eine store(), save() o.ä. Methode? Das ist schwer zu
261: * finden und vor allem schwer nachzuvollziehen...
262: */
263: public void close() throws StructureException {
264: if (isOpen()) {
265: if (saveBeforeClosing()) {
266: try {
267: getLogger()
268: .debug(
269: getLanguage()
270: .getString(
271: "Structures.XMLStructure.Msg.SavingFile")
272: + ": " + filename);
273:
274: FileOutputStream out = new FileOutputStream(
275: filename);
276: XMLOutputter xmlOutputter = new XMLOutputter(
277: getCustomization().getString(
278: customizationPrefix + ".Indent",
279: " "), true, getCustomization()
280: .getString(
281: customizationPrefix
282: + ".Charset",
283: "iso-8859-1"));
284: createRootElement();
285:
286: Document xmlDocument = new Document(rootElement);
287: xmlOutputter.output(xmlDocument, out);
288: out.close();
289:
290: if (root != null) {
291: replacePositionRecordable(root);
292: } else {
293: System.out.println("Root null");
294: }
295: } catch (IOException i) {
296: throw new StructureException(
297: getLanguage()
298: .getString(
299: "Structures.XMLStructure.Error.Write")
300: + ": "
301: + filename
302: + ";\n"
303: + i.getMessage());
304: }
305: }
306:
307: isOpen = false;
308: fireStructureChange(new StructureChangeEvent(
309: StructureChangeEvent.STRUCTURE_CLOSED, null));
310: } else {
311: throw new StructureException(getLanguage().getString(
312: "Structures.Structure.Error.AlreadyClosed"));
313: }
314: }
315:
316: /**
317: * Creates the root element of the xml structure.
318: */
319: public void createRootElement() {
320: rootElement.setName(getCustomization().getString(
321: customizationPrefix + ".Root", defaultRoot));
322: rootElement.addContent(rootRecord);
323: rootElement.addContent(rootStructure);
324: }
325:
326: /**
327: * Returns whether the structure is open or not.
328: *
329: * @return <code>true</code> if the structure is open, otherwise <code>false</code>
330: */
331: public boolean isOpen() {
332: return isOpen;
333: }
334:
335: /**
336: * Clears all data from this structure. After invoking this method the structure does not have
337: * any elements.
338: *
339: * @throws StructureException if the structure is not open
340: */
341: public void clear() throws StructureException {
342: if (isOpen()) {
343: getLogger().debug(
344: getLanguage().getString(
345: "Structures.Structure.Msg.Clear"));
346: rootElement.removeChildren();
347: rootRecord.removeChildren();
348: rootStructure.removeChildren();
349: fireStructureChange(new StructureChangeEvent(
350: StructureChangeEvent.STRUCTURE_CLEARED, null));
351: } else {
352: throw new StructureException(getLanguage().getString(
353: "Structures.Structure.Error.ClearNotOpen"));
354: }
355: }
356:
357: /**
358: * Returns the number of childs of root stored in the xml structure.
359: *
360: * @return DOCUMENT ME!
361: *
362: * @throws StructureException if the structure is not open
363: */
364: public int size() throws StructureException {
365: if (isOpen()) {
366: return rootElement.getChildren().size();
367: } else {
368: throw new StructureException(getLanguage().getString(
369: "Structures.Structure.Error.SizeNotOpen"));
370: }
371: }
372:
373: /**
374: * Set the root element of the structure. The root element and all its children will be saved
375: * in the external structure. Should throw a {@link StructureException} if the structure is
376: * not open, for example.
377: *
378: * @param root the root element of the structure that should be saved.
379: *
380: * @throws StructureException if an error has occurred
381: */
382: public void setRootElement(StructureElement root)
383: throws StructureException {
384: if (isOpen()) {
385: if (root != null) {
386: this .root = root;
387:
388: rootStructure.removeChildren();
389:
390: replaceRecordable(root);
391:
392: if (selectionStructureConverter.handlesElement(root)) {
393: Object toAdd = selectionStructureConverter
394: .convertElement(root);
395:
396: if (toAdd != null) {
397: rootStructure.addContent((Element) toAdd);
398:
399: // notify listeners
400: StructureElement[] addedElements = new StructureElement[1];
401: addedElements[0] = root;
402: fireStructureChange(new StructureChangeEvent(
403: StructureChangeEvent.STRUCTUREELEMENT_ADDED,
404: addedElements));
405: }
406: }
407: }
408: } else {
409: throw new StructureException(getLanguage().getString(
410: "Structures.Structure.Error.StructureNotOpen"));
411: }
412: }
413:
414: /**
415: * Sets the record of the recordables. Should throw a {@link StructureException} if the
416: * structure is not open, for example.
417: *
418: * @param record the record that should be stored
419: *
420: * @throws StructureException if an error has occurred
421: */
422: public void setRecord(ReadAccessRecord record)
423: throws StructureException {
424: if (isOpen()) {
425: systemRecord = record;
426:
427: try {
428: this .record = record.toArray();
429: } catch (RecordException r) {
430: throw new StructureException(
431: getLanguage()
432: .getString(
433: "Structures.Structure.Error.RecordNotAccessible"));
434: }
435:
436: for (int i = 0; i < this .record.length; i++) {
437: Recordable element = this .record[i];
438:
439: if (element != null) {
440: if (selectionRecordConverter
441: .handlesElement(element)) {
442: Object toAdd = null;
443:
444: try {
445: toAdd = selectionRecordConverter
446: .convertElement(element);
447: } catch (IllegalNameException ex) {
448: logger
449: .error(language
450: .getString("Structures.XMLStructure.Error.ConvertName")
451: + " "
452: + element.getElementName());
453: } catch (IllegalDataException ex) {
454: logger
455: .error(language
456: .getString("Structures.XMLStructure.Error.ConvertData")
457: + " "
458: + element.getElementName());
459: }
460:
461: if (toAdd != null) {
462: rootRecord.addContent((Element) toAdd);
463:
464: // notify listeners
465: StructureElement[] addedElements = new StructureElement[1];
466: addedElements[0] = element;
467: fireStructureChange(new StructureChangeEvent(
468: StructureChangeEvent.STRUCTUREELEMENT_ADDED,
469: addedElements));
470: }
471: } else {
472: String className = element.getClass().getName();
473: UnknownRecordable unknownRecordable = new UnknownRecordable(
474: env, className);
475: Object toAdd = selectionRecordConverter
476: .convertElement(unknownRecordable);
477: rootRecord.addContent((Element) toAdd);
478:
479: // notify listeners
480: StructureElement[] addedElements = new StructureElement[1];
481: addedElements[0] = element;
482: fireStructureChange(new StructureChangeEvent(
483: StructureChangeEvent.STRUCTUREELEMENT_ADDED,
484: addedElements));
485: getLogger()
486: .warn(
487: getLanguage()
488: .getString(
489: "Structures.XMLStructure.Warn.UnknownRecordable")
490: + ": " + className);
491: }
492: }
493: }
494: } else {
495: throw new StructureException(getLanguage().getString(
496: "Structures.Structure.Error.StructureNotOpen"));
497: }
498: }
499:
500: /**
501: * Whether the structure should be stored to the xml file before it will be closed or not. If
502: * the specified value is <code>false</code>, all changes of the record will get lost.
503: *
504: * @param saveBeforeClosing DOCUMENT ME!
505: */
506: public void setSaveBeforeClosing(boolean saveBeforeClosing) {
507: this .saveBeforeClosing = saveBeforeClosing;
508: }
509:
510: /**
511: * Returns whether the structure will be stored to the xml file before it will be closed or
512: * not.
513: *
514: * @return DOCUMENT ME!
515: */
516: public boolean saveBeforeClosing() {
517: return saveBeforeClosing;
518: }
519:
520: /**
521: * Returns the Children of the root. If there is no converter registered for a sort of xml
522: * elements, they will not be in the structure.
523: *
524: * @return structure elements the childs of the root structure elment
525: */
526: public StructureElement getRootElement() {
527: return root;
528: }
529:
530: /**
531: * Returns the Children of the root. If there is no converter registered for a sort of xml
532: * elements, they will not be in the structure.
533: *
534: * @return structure elements the childs of the root structure elment
535: *
536: * @throws StructureException if the structure is not open
537: */
538: public ReadAccessRecord getRecord() throws StructureException {
539: VectorRecord result = new VectorRecord(env);
540:
541: try {
542: result.open();
543:
544: for (int i = 0; i < record.length; i++) {
545: result.record(record[i]);
546: }
547: } catch (RecordException r) {
548: throw new StructureException(getLanguage().getString(
549: "Structures.Structure.Error.RecordConstruction"));
550: }
551:
552: return result;
553: }
554:
555: /**
556: * Returns the filename of this structure.
557: *
558: * @return guess!
559: */
560: public String getFilename() {
561: return filename;
562: }
563:
564: /**
565: * Create the record from the xml file
566: */
567: private void createRecord() {
568: Vector result = new Vector();
569: Iterator iterator = rootRecord.getChildren().iterator();
570:
571: while (iterator.hasNext()) {
572: Object internalRepresentation = iterator.next();
573:
574: if (selectionRecordConverter
575: .handlesOther(internalRepresentation)) {
576: StructureElement toAdd = selectionRecordConverter
577: .convertOther(internalRepresentation);
578:
579: if (toAdd != null) {
580: result.add(toAdd);
581: }
582: }
583: }
584:
585: record = new Recordable[result.size()];
586:
587: Recordable recordable;
588:
589: for (int i = 0; i < result.size(); i++) {
590: recordable = (Recordable) result.get(i);
591:
592: // check alignments
593: if (recordable instanceof MediaClipRecordable) {
594: String alignedToUUID = ((MediaClipRecordable) recordable)
595: .getAlignedToUUID();
596:
597: if (alignedToUUID != "") {
598: Recordable check;
599: Iterator iter = result.iterator();
600:
601: while (iter.hasNext()) {
602: check = (Recordable) iter.next();
603:
604: if (check.getUUIDString().equals(alignedToUUID)) {
605: ((MediaClipRecordable) recordable)
606: .setAlignedTo(check);
607: }
608: }
609: }
610: }
611:
612: record[i] = recordable;
613: }
614: }
615:
616: /**
617: * Returns the name of the stored record.
618: *
619: * @return the name of the record as String
620: */
621: public String getRecordName() {
622: if (rootRecord.getAttributeValue("name") != null) {
623: return rootRecord.getAttributeValue("name");
624: } else {
625: return "";
626: }
627: }
628:
629: /**
630: * Sets the name of the stored record.
631: *
632: * @param recordName DOCUMENT ME!
633: */
634: public void setRecordName(String recordName) {
635: rootRecord.setAttribute("name", recordName);
636: }
637:
638: /**
639: * Create the structure from the xml file
640: */
641: private void createStructure() {
642: Iterator iterator = rootStructure.getChildren().iterator();
643:
644: if (iterator.hasNext()) {
645: Object internalRepresentation = iterator.next();
646:
647: if (selectionStructureConverter
648: .handlesOther(internalRepresentation)) {
649: StructureElement toAdd = selectionStructureConverter
650: .convertOther(internalRepresentation);
651:
652: if (toAdd != null) {
653: if (!(toAdd instanceof PositionRecordable)) {
654: replacePositionRecordable(toAdd);
655:
656: StructureElement[] children = toAdd
657: .getChildren("jacareto.struct.SymbolStructureElement");
658:
659: for (int i = children.length - 1; i >= 0; i--) {
660: StructureElement realElement = ((SymbolStructureElement) children[i])
661: .createRealElement(classLoader);
662:
663: if (realElement != null) {
664: StructureElement parent = (StructureElement) children[i]
665: .getParent();
666:
667: if (parent != null) {
668: int index = parent
669: .getIndex(children[i]);
670: parent.removeChild(children[i]);
671: parent.insertChild(realElement,
672: index);
673: } else {
674: toAdd = realElement;
675: }
676: }
677: }
678:
679: root = toAdd;
680: }
681: }
682: }
683: } else {
684: System.err.println("no next");
685: }
686:
687: // Try to set the root type
688: if (root != null) {
689: Hashtable typesMap = customization.getMap(
690: "RootElement.Types", new EnhancedHashtable());
691: Enumeration enumeration = typesMap.keys();
692:
693: while (enumeration.hasMoreElements()) {
694: String rootElementName = (String) enumeration
695: .nextElement();
696: String rootKey = (String) typesMap.get(rootElementName);
697: String rootClass = customization.getString(rootKey
698: + ".Class", "");
699:
700: if (rootClass.equals(root.getClass().getName())) {
701: customization.put("RootElement.Type", rootKey);
702:
703: break;
704: }
705: }
706: }
707: }
708:
709: /**
710: * Replaces all PositionRecordables in the structure with the real recordables.
711: *
712: * @param toAdd the element that children should replaced.
713: */
714: private void replacePositionRecordable(StructureElement toAdd) {
715: StructureElement[] children = toAdd
716: .getChildren("jacareto.record.PositionRecordable");
717:
718: for (int i = 0; i < children.length; i++) {
719: StructureElement parent = (StructureElement) children[i]
720: .getParent();
721: int index = parent.getIndex(children[i]);
722: int position = ((PositionRecordable) children[i])
723: .getPosition();
724:
725: if ((position != -1) && (position < record.length)) {
726: parent.removeChild(children[i]);
727: parent.insertChild(record[position], index);
728: }
729: }
730: }
731:
732: /**
733: * Replace all Recordable with PositionRecordable
734: *
735: * @param elem the element whose children should be replaced.
736: */
737: private void replaceRecordable(StructureElement elem) {
738: StructureElement[] children = elem
739: .getChildren("jacareto.record.Recordable");
740:
741: for (int i = 0; i < children.length; i++) {
742: StructureElement parent = (StructureElement) children[i]
743: .getParent();
744: int index = parent.getIndex(children[i]);
745: int position = -1;
746:
747: try {
748: position = systemRecord
749: .getIndex((Recordable) children[i]);
750:
751: if ((position != -1)
752: && (position < systemRecord.size())) {
753: parent.removeChild(children[i]);
754: parent.insertChild(new PositionRecordable(env,
755: position), index);
756: }
757: } catch (RecordException r) {
758: getLogger()
759: .error(
760: getLanguage()
761: .getString(
762: "Structures.XMLStructure.error.CanntConvertRecordable"));
763: r.printStackTrace();
764: }
765: }
766: }
767: }
|