001: package org.enhydra.shark.xpdl;
002:
003: import java.io.Serializable;
004: import java.util.ArrayList;
005: import java.util.Iterator;
006: import java.util.List;
007:
008: /**
009: * Base class for representing elements from XML schema.
010: *
011: * @author Sasa Bojanic
012: */
013: public abstract class XMLElement implements Serializable, Cloneable {
014:
015: protected transient List listeners = new ArrayList();
016:
017: protected transient boolean notifyMainListeners = false;
018: protected transient boolean notifyListeners = false;
019:
020: protected Integer originalElementHashCode;
021:
022: /**
023: * Equivalent for XML element name. Used when writting instance of this class to XML file.
024: */
025: private String name;
026:
027: /**
028: * Indicates if element is required - corresponds to the same XML element definition.
029: */
030: private boolean isRequired = false;
031:
032: /**
033: * Supposed to contain the value for XML element. This is true for simple elements and
034: * attributes, more complex elements uses it as they need.
035: */
036: protected String value;
037:
038: /**
039: * Indicates if an element is read only.
040: */
041: protected boolean isReadOnly = false;
042:
043: /**
044: * Reference to parent object in DOM tree.
045: */
046: protected XMLElement parent;
047:
048: /**
049: * Creates a new instance of element: sets <code>name</code> to name of concrete class
050: * implementation of this abstract class, and <code>parent</code> and <code>isRequired</code>
051: * properties to the specified ones.
052: * <p>
053: * It also sets the value of this element to an empty String.
054: */
055: public XMLElement(XMLElement parent, boolean isRequired) {
056: this .parent = parent;
057: this .isRequired = isRequired;
058: this .name = getClass().getName();
059: this .name = XMLUtil.getShortClassName(name);
060: this .value = new String();
061: originalElementHashCode = new Integer(this .hashCode());
062: }
063:
064: /**
065: * Creates a new instance of element: sets <code>name</code>,
066: * <code>parent</code> <code>isRequired</code> properties to specified ones.
067: * <p>
068: * It also sets the value of this element to an empty String.
069: */
070: public XMLElement(XMLElement parent, String name, boolean isRequired) {
071: this .parent = parent;
072: this .name = name;
073: this .isRequired = isRequired;
074: this .value = new String();
075: originalElementHashCode = new Integer(this .hashCode());
076: }
077:
078: public void makeAs(XMLElement el) {
079: if (!(el != null && el.getClass().equals(this .getClass()) && el.name
080: .equals(this .name))) {
081: throw new RuntimeException("Can't perform makeAs!");
082: }
083: setValue(el.value);
084: }
085:
086: /**
087: * Sets 'read only' property of element to specified value. This enables/disables editing of the
088: * element value for the simple elements and attributes, or changes to attributes and elements of
089: * complex objects and collections.
090: * <p>
091: * If element is read only, and one wants to change its property, the RuntimeException will be
092: * thrown.
093: */
094: public void setReadOnly(boolean ro) {
095: this .isReadOnly = ro;
096: }
097:
098: /**
099: * Returns the 'read only' status of element.
100: * <p>
101: * If element is read only, and one wants to change its property, the RuntimeException will be
102: * thrown.
103: */
104: public boolean isReadOnly() {
105: return isReadOnly;
106: }
107:
108: /**
109: * Returns if the element is required or not, which is defined by XPDL schema. If element is
110: * required, its value must be defined (In the case of complex elements, all the required
111: * subelements must be defined). Otherwise, the whole Package won't be valid by the XPDL schema.
112: */
113: public boolean isRequired() {
114: return isRequired;
115: }
116:
117: /**
118: * Indicates if element is empty.
119: */
120: public boolean isEmpty() {
121: return !(value != null && value.trim().length() > 0);
122: }
123:
124: /**
125: * Sets the element value. If it is simple element or an non-choice attribute, this sets the
126: * actual value of the element. If it is choice attribute, it sets the choosen value. Only some
127: * complex elements (Condition, SchemaType, and ExtendedAttribute) allows you to use this method,
128: * while others will throw RuntimeException.
129: */
130: public void setValue(String v) {
131: if (isReadOnly) {
132: String elInfo = "Name=" + toName() + ", Val=" + toValue();
133: if (parent != null) {
134: elInfo += ", Parent name=" + parent.toName();
135: }
136: throw new RuntimeException(
137: "Can't set the value of read only element: "
138: + elInfo + "!");
139: }
140: boolean notify = false;
141: String oldValue = value;
142: if (!this .value.equals(v)) {
143: notify = true;
144: }
145: this .value = v;
146:
147: if (notify && (notifyMainListeners || notifyListeners)) {
148: XMLElementChangeInfo info = createInfo(oldValue, value,
149: null, XMLElementChangeInfo.UPDATED);
150: if (notifyListeners) {
151: notifyListeners(info);
152: }
153: if (notifyMainListeners) {
154: notifyMainListeners(info);
155: }
156: }
157: }
158:
159: /**
160: * Returns the element value.
161: */
162: public String toValue() {
163: return value;
164: }
165:
166: /**
167: * Returns the name of element.
168: */
169: public String toName() {
170: return name;
171: }
172:
173: /** Gets the parent element in DOM tree. */
174: public XMLElement getParent() {
175: return parent;
176: }
177:
178: /**
179: * Sets the parent element in DOM tree.
180: * <p>
181: * This method is used when collection, complex element or complex choice is cloned, to set new
182: * parent element of the cloned sub-elements.
183: */
184: public void setParent(XMLElement el) {
185: this .parent = el;
186: }
187:
188: /**
189: * Used to create exact copy of the element.
190: */
191: public Object clone() {
192: // NOTE: DO NOT MAKE A originalHashCode as new Integer(this.hasCode())
193: // CHECK: is the above case for the "name" and "value" ????????
194: XMLElement d = null;
195: try {
196: // System.out.println("Cloning XMLELement "+this);
197: d = (XMLElement) super .clone();
198: d.parent = this .parent;
199: d.name = new String(this .name);
200: d.value = new String(this .value);
201: d.isRequired = this .isRequired;
202: d.isReadOnly = this .isReadOnly;
203: d.listeners = new ArrayList();
204: d.notifyListeners = false;
205: d.notifyMainListeners = false;
206: //d.parent = this.parent;
207: } catch (CloneNotSupportedException ex) {
208: // Won't happen because we implement Cloneable
209: throw new Error(ex.toString());
210: }
211: // System.out.println("return cloned XMLELement "+d);
212: return d;
213: }
214:
215: public Integer getOriginalElementHashCode() {
216: return originalElementHashCode;
217: }
218:
219: public boolean equals(Object e) {
220: //System.out.println("Checking eq for el "+e+" with el "+this);
221: if (this == e) {
222: //System.out.println(" Elements are identical");
223: return true;
224: }
225: boolean equals = false;
226: if (e != null && e instanceof XMLElement
227: && e.getClass().equals(this .getClass())) {
228: XMLElement el = (XMLElement) e;
229: // TODO: do we need to check isReadOnly for equality?
230: equals = this .name.equals(el.name);
231: //System.out.println(" Element names equal - "+equals);
232: equals = equals && this .value.equals(el.value);
233: //System.out.println(" Element values equal - "+equals);
234: equals = equals && (this .isRequired == el.isRequired);
235: //System.out.println(" Element required equal - "+equals);
236: //&& (this.parent == null ? el.parent == null : this.parent.equals(el.parent)));
237: } else {
238: // System.out.println(" Els are not the same class: el="+e+", this="+this);
239:
240: }
241: //System.out.println(" equals final - "+equals);
242: return equals;
243: }
244:
245: public List getListeners() {
246: if (listeners == null) {
247: listeners = new ArrayList();
248: }
249: return new ArrayList(listeners);
250: }
251:
252: public void addListener(XMLElementChangeListener listener) {
253: if (listeners == null) {
254: listeners = new ArrayList();
255: }
256: listeners.add(listener);
257: // System.out.println("ADDED listener for element "+this+", hc="+this.hashCode());
258: }
259:
260: public boolean removeListener(XMLElementChangeListener listener) {
261: if (listeners == null) {
262: listeners = new ArrayList();
263: }
264: // System.out.println("REMOVED listener for element "+this+", hc="+this.hashCode());
265: return listeners.remove(listener);
266: }
267:
268: protected void notifyListeners(XMLElementChangeInfo info) {
269: Iterator it = getListeners().iterator();
270: while (it.hasNext()) {
271: XMLElementChangeListener listener = (XMLElementChangeListener) it
272: .next();
273: listener.xmlElementChanged(info);
274: }
275: }
276:
277: protected void notifyMainListeners(XMLElementChangeInfo info) {
278: XMLElement main = getMainElement();
279: if (main != null) {
280: Iterator it = main.getListeners().iterator();
281: while (it.hasNext()) {
282: XMLElementChangeListener listener = (XMLElementChangeListener) it
283: .next();
284: listener.xmlElementChanged(info);
285: }
286: }
287: }
288:
289: protected XMLElement getMainElement() {
290: XMLElement el = this ;
291: while (!el.isMainElement()) {
292: el = el.getParent();
293: if (el == null)
294: break;
295: }
296: return el;
297: }
298:
299: protected boolean isMainElement() {
300: return false;
301: }
302:
303: public void setNotifyMainListeners(boolean notify) {
304: this .notifyMainListeners = notify;
305: }
306:
307: public void setNotifyListeners(boolean notify) {
308: this .notifyListeners = notify;
309: }
310:
311: protected XMLElementChangeInfo createInfo(Object oldVal,
312: Object newVal, List changedSubElements, int action) {
313: XMLElementChangeInfo info = new XMLElementChangeInfo();
314: info.setChangedElement(this);
315: info.setOldValue(oldVal);
316: info.setNewValue(newVal);
317: info.setChangedSubElements(changedSubElements);
318: info.setAction(action);
319: return info;
320: }
321:
322: }
|