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.parse.RecordTokenizer;
027: import jacareto.record.Record;
028: import jacareto.record.RecordChangeEvent;
029: import jacareto.record.RecordChangeListener;
030: import jacareto.record.RecordException;
031: import jacareto.record.Recordable;
032: import jacareto.record.VectorRecord;
033: import jacareto.system.Customization;
034: import jacareto.system.Environment;
035: import jacareto.system.Language;
036: import jacareto.toolkit.EnhancedHashtable;
037: import jacareto.toolkit.PriorityList;
038:
039: import org.apache.log4j.Logger;
040:
041: import java.lang.reflect.Constructor;
042: import java.lang.reflect.Method;
043:
044: import java.util.Enumeration;
045: import java.util.Iterator;
046: import java.util.Vector;
047:
048: /**
049: * The root element of a structure.
050: *
051: * @author <a href="mailto:cspannagel@web.de">Christian Spannagel</a>
052: * @version 1.01
053: */
054: public class RootElement extends StructureElement implements
055: RecordChangeListener {
056: static {
057: setClassLoader(ClassLoader.getSystemClassLoader());
058: }
059:
060: /** The list of replayers. */
061: private static PriorityList parseMethods;
062:
063: /** The name of the root element. */
064: private static String name;
065:
066: /** The description if the root element. */
067: private static String description;
068:
069: /** The class loader which loads the structure element classes. */
070: private static ClassLoader classLoader;
071:
072: /** The record. */
073: private Record record;
074:
075: /**
076: * Creates a new default root structure element.
077: *
078: * @param env the environment
079: * @param children the child structure elements
080: */
081: public RootElement(Environment env, StructureElement[] children) {
082: super (env, children);
083: }
084:
085: /**
086: * Creates a new default root structure element with no childs.
087: *
088: * @param env the environment
089: */
090: public RootElement(Environment env) {
091: super (env);
092: }
093:
094: /**
095: * Sets the class loader which loads the structure element classes.
096: *
097: * @param newClassLoader DOCUMENT ME!
098: */
099: public static void setClassLoader(ClassLoader newClassLoader) {
100: classLoader = newClassLoader;
101: }
102:
103: /**
104: * Parses a record which is tokenized by the given record tokenizer. Which root element type is
105: * used is defined by the customization key "RootElement.Type".
106: *
107: * @param env DOCUMENT ME!
108: * @param recordTokenizer the record tokenizer
109: *
110: * @return the structure element, or <code>null</code> if this class cannot parse the record at
111: * the current position
112: */
113: public static StructureElement parse(Environment env,
114: RecordTokenizer recordTokenizer) {
115: return parse(env, recordTokenizer, env.getCustomization()
116: .getString("RootElement.Type", "DefaultRootElement"));
117: }
118:
119: /**
120: * Parses a record which is tokenized by the given record tokenizer.
121: *
122: * @param env DOCUMENT ME!
123: * @param recordTokenizer the record tokenizer
124: * @param customizationKey DOCUMENT ME!
125: *
126: * @return the structure element for the given customization key, or <code>null</code> if this
127: * class cannot parse the record at the current position
128: */
129: public static StructureElement parse(Environment env,
130: RecordTokenizer recordTokenizer, String customizationKey) {
131: Logger logger = env.getLogger();
132: Language language = env.getLanguage();
133: Customization customization = env.getCustomization();
134:
135: name = language.getString("Structures." + customizationKey
136: + ".Name");
137: description = language.getString("Structures."
138: + customizationKey + ".Description");
139:
140: loadStructures(env, customizationKey);
141:
142: Object[] parameters = new Object[2];
143: parameters[0] = env;
144: parameters[1] = recordTokenizer;
145:
146: StructureElement[] children;
147: Vector childrenVector = new Vector(0, 5);
148:
149: boolean isStructureBuilding = customization.getBoolean(
150: "RootElement.BuildStructure", true);
151:
152: while (recordTokenizer.hasMore()) {
153: StructureElement element = null;
154:
155: if (isStructureBuilding) {
156: Iterator i = parseMethods.iterator();
157:
158: // parse the record at the current position
159: while (i.hasNext() && (element == null)) {
160: Method parseMethod = (Method) i.next();
161:
162: try {
163: element = (StructureElement) parseMethod
164: .invoke(null, parameters);
165: } catch (Throwable t) {
166: logger
167: .error(language
168: .getString("Structures.DefaultRootElement.Error.MethodInvocation"));
169: logger.error(parseMethod.getDeclaringClass()
170: .getName());
171: }
172: }
173: }
174:
175: // add the structure element or the recordable to the children vector
176: if (element != null) {
177: childrenVector.add(element);
178: } else {
179: childrenVector.add(recordTokenizer.get());
180: recordTokenizer.forward();
181: }
182: }
183:
184: // Converting vector to array
185: children = new StructureElement[childrenVector.size()];
186:
187: for (int j = 0; j < childrenVector.size(); j++) {
188: children[j] = (StructureElement) childrenVector.get(j);
189: }
190:
191: // Create the root element
192: try {
193: Class rootClass = Class.forName(customization.getString(
194: customizationKey + ".Class",
195: "jacareto.struct.RootElement"), true, classLoader);
196: Class[] parameterTypes = new Class[2];
197: parameterTypes[0] = env.getClass();
198: parameterTypes[1] = children.getClass();
199:
200: Constructor constructor = rootClass
201: .getConstructor(parameterTypes);
202: Object[] cParameters = new Object[2];
203: cParameters[0] = env;
204: cParameters[1] = children;
205:
206: return (StructureElement) constructor
207: .newInstance(cParameters);
208: } catch (Exception e) {
209: return new RootElement(env, children);
210: }
211: }
212:
213: /**
214: * Parses a record which is tokenized by the given structure. Which root element type is used
215: * is defined by the customization key "RootElement.Type".
216: *
217: * @param env the envoriment
218: * @param root the root structure element taht schoul be parsed
219: *
220: * @return the structure element, or <code>null</code> if this class cannot parse the record at
221: * the current position
222: */
223: public static StructureElement parse(Environment env,
224: StructureElement root) {
225: return parse(env, root, env.getCustomization().getString(
226: "RootElement.Type", "DefaultRootElement"));
227: }
228:
229: /**
230: * Parses a record which is tokenized by the given structure. Used when static structure
231: * elements contained in the tree with the root element <code>root</code> should be preserved.
232: *
233: * @param env the envoriment
234: * @param root the root structure element that should be parsed
235: * @param customizationKey DOCUMENT ME!
236: *
237: * @return the structure element for the given customization key, or <code>null</code> if this
238: * class cannot parse the record at the current position
239: */
240: public static StructureElement parse(Environment env,
241: StructureElement root, String customizationKey) {
242: Logger logger = env.getLogger();
243: Customization customization = env.getCustomization();
244:
245: StructureElement[] children = root.getChildren();
246: StructureElement result = null;
247: VectorRecord record = (VectorRecord) root.getRoot().getRecord();
248: Constructor constructor = null;
249: Object[] cParameters = null;
250: boolean loadedByConstructor = false;
251:
252: // Create the root element
253: try {
254: Class rootClass = Class.forName(customization.getString(
255: customizationKey + ".Class",
256: "jacareto.struct.RootElement"), true, classLoader);
257: Class[] parameterTypes = new Class[2];
258: parameterTypes[0] = env.getClass();
259:
260: StructureElement[] dummy = new StructureElement[1];
261: parameterTypes[1] = dummy.getClass();
262: constructor = rootClass.getConstructor(parameterTypes);
263: cParameters = new Object[2];
264: cParameters[0] = env;
265: cParameters[1] = new StructureElement[0];
266: result = (StructureElement) constructor
267: .newInstance(cParameters);
268: loadedByConstructor = true;
269: } catch (Exception e) {
270: result = new RootElement(env, new StructureElement[0]);
271: }
272:
273: try {
274: VectorRecord helpRecord = new VectorRecord(env);
275: helpRecord.open();
276:
277: for (int i = 0; i < children.length; i++) {
278: if (!children[i].isStatic()) {
279: if (!(children[i] instanceof Recordable)) {
280: StructureElement[] toAdd = children[i]
281: .getChildren("jacareto.record.Recordable");
282: StructureElement[] toAdded = new StructureElement[toAdd.length];
283: int offset = -1;
284:
285: // Get the lowest record index of the children and
286: // store it in offset
287: for (int j = 0; j < toAdd.length; j++) {
288: int index = record
289: .getIndex((Recordable) toAdd[j]);
290:
291: if (offset == -1) {
292: offset = index;
293: }
294:
295: if (index < offset) {
296: offset = index;
297: }
298: }
299:
300: // Next loop throws sometimes an ArrayIndexOutOfBoundsException
301: int loopindex = 0;
302: int index = 0;
303:
304: try {
305: for (loopindex = 0; loopindex < toAdd.length; loopindex++) {
306: index = record
307: .getIndex((Recordable) toAdd[loopindex])
308: - offset;
309: toAdded[index] = toAdd[loopindex];
310: }
311: } catch (ArrayIndexOutOfBoundsException a) {
312: System.out.println("Children[i]: "
313: + children[i].toShortString());
314: System.out.println("toAdd[loopindex]: "
315: + toAdd[loopindex].toShortString());
316: System.out
317: .println("Array Index Out Of Bounds!");
318: System.out.println("loopindex: "
319: + loopindex);
320: System.out.println("index: " + index);
321: System.out.println("offset: " + offset);
322: System.out.println("toAdded.length: "
323: + toAdded.length);
324: System.out.println("toAdd.length: "
325: + toAdd.length);
326: throw (a);
327: }
328:
329: for (int j = 0; j < toAdd.length; j++) {
330: helpRecord.record((Recordable) toAdded[j]);
331: }
332: } else {
333: helpRecord.record((Recordable) children[i]);
334: }
335: } else {
336: if (helpRecord.size() > 0) {
337: RootElement help = (RootElement) RootElement
338: .parse(env, new RecordTokenizer(env,
339: helpRecord), customizationKey);
340:
341: result.addChildren(help.getChildren());
342: helpRecord.clear();
343: }
344:
345: RootElement help = (RootElement) RootElement.parse(
346: env, children[i], customizationKey);
347: children[i].setChildren(help.getChildren());
348: result.addChild(children[i]);
349: }
350: }
351:
352: if (helpRecord.size() > 0) {
353: RootElement help = (RootElement) RootElement.parse(env,
354: new RecordTokenizer(env, helpRecord),
355: customizationKey);
356:
357: result.addChildren(help.getChildren());
358: helpRecord.clear();
359: }
360: } catch (RecordException e) {
361: logger.error(e.toString());
362: }
363:
364: // Call the constructor again for setting root element attributes
365: // regarding to the children
366: if (loadedByConstructor) {
367: try {
368: cParameters[1] = result.getChildren();
369: result = (StructureElement) constructor
370: .newInstance(cParameters);
371: } catch (Exception e) {
372: result = new RootElement(env, result.getChildren());
373: }
374: } else {
375: result = new RootElement(env, result.getChildren());
376: }
377:
378: return result;
379: }
380:
381: /**
382: * Register a parse method of a structure element with a specified priority. A greater value of
383: * priority symbolizes a higher priority.
384: *
385: * @param method the method to add
386: * @param priority the priority
387: */
388: public static void addParseMethod(Method method, int priority) {
389: if (priority >= 0) {
390: parseMethods.add(method, priority);
391: } else {
392: parseMethods.add(method);
393: }
394: }
395:
396: /**
397: * Register a parse method of a structure element with the lowest priority.
398: *
399: * @param method the parse method to add
400: */
401: public static void addParseMethod(Method method) {
402: addParseMethod(method, -1);
403: }
404:
405: /**
406: * Loads the structure elements from the {@link jacareto.system.Customization} instance of the
407: * {@link jacareto.system.Environment} object.
408: *
409: * @param env the environment
410: * @param customizationKey the customization key of the root element
411: */
412: private static void loadStructures(Environment env,
413: String customizationKey) {
414: parseMethods = new PriorityList();
415:
416: Logger logger = env.getLogger();
417: Customization customization = env.getCustomization();
418: Language language = env.getLanguage();
419:
420: if (classLoader == null) {
421: classLoader = ClassLoader.getSystemClassLoader();
422: }
423:
424: try {
425: // some reflection instances
426: Class[] parameterTypes = new Class[2];
427: parameterTypes[0] = Class.forName(
428: "jacareto.system.Environment", true, classLoader);
429: parameterTypes[1] = Class
430: .forName("jacareto.parse.RecordTokenizer", true,
431: classLoader);
432:
433: // load the map of registered structure elements
434: EnhancedHashtable structMap = customization.getMap(
435: customizationKey + ".Structures",
436: new EnhancedHashtable());
437: Enumeration enumeration = structMap.keys();
438:
439: while (enumeration.hasMoreElements()) {
440: String structClassName = (String) enumeration
441: .nextElement();
442: Class structClass = Class.forName(structClassName,
443: true, classLoader);
444: Method parseMethod = structClass.getMethod("parse",
445: parameterTypes);
446: int priority = structMap.getInt(structClassName, -1);
447: addParseMethod(parseMethod, priority);
448: logger
449: .debug(language
450: .getString("Structures.DefaultRootElement.Msg.Loading")
451: + " "
452: + structClassName
453: + ", "
454: + language
455: .getString("General.Priority")
456: + ": " + priority);
457: }
458: } catch (ClassNotFoundException c) {
459: logger
460: .error(
461: language
462: .getString("Structures.DefaultRootElement.Error.MissingClass"),
463: c);
464: } catch (NoSuchMethodException n) {
465: logger
466: .error(
467: language
468: .getString("Structures.DefaultRootElement.Error.MissingMethod"),
469: n);
470: }
471: }
472:
473: /**
474: * Returns the name of the element.
475: *
476: * @return the name
477: */
478: public String getElementName() {
479: String result = "";
480: String recordName = record.getRecordName();
481:
482: if ((recordName == null) || recordName.equals("")) {
483: result = language.getString("Record.NoName");
484: } else {
485: result = recordName;
486: }
487:
488: result += (" (" + name + ")");
489:
490: return result;
491: }
492:
493: /**
494: * Returns a description of the element.
495: *
496: * @return the description
497: */
498: public String getElementDescription() {
499: return description;
500: }
501:
502: /**
503: * Returns a String which describes the content of the element shortly.
504: *
505: * @return a string with a short description of the element
506: */
507: public String toShortString() {
508: return getElementName();
509: }
510:
511: /**
512: * Sets the record the structure of this root element belongs to.
513: *
514: * @param record DOCUMENT ME!
515: */
516: public void setRecord(Record record) {
517: if (this .record != null) {
518: this .record.removeRecordChangeListener(this );
519: }
520:
521: this .record = record;
522: this .record.addRecordChangeListener(this );
523: }
524:
525: /**
526: * Returns the record the structure of this root element belongs to.
527: *
528: * @return the record, or <code>null</code> if the record has not been set before
529: */
530: public Record getRecord() {
531: return record;
532: }
533:
534: /**
535: * Invoked when the record has changed.
536: *
537: * @param event the event which contains the information of the record change.
538: */
539: public void recordHasChanged(RecordChangeEvent event) {
540: switch (event.getID()) {
541: case RecordChangeEvent.RECORD_NAME_CHANGED:
542: fireValuesChanged();
543:
544: break;
545:
546: default:
547: break;
548: }
549: }
550:
551: /**
552: * Clones the element.
553: *
554: * @return DOCUMENT ME!
555: */
556: public Object clone() {
557: // To be improved
558: StructureElement[] clonedChildren = getClonedChildren();
559:
560: return new RootElement(env, clonedChildren);
561: }
562: }
|