001: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
002: *
003: * ***** BEGIN LICENSE BLOCK *****
004: * Version: MPL 1.1/GPL 2.0
005: *
006: * The contents of this file are subject to the Mozilla Public License Version
007: * 1.1 (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: * http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the
014: * License.
015: *
016: * The Original Code is Rhino code, released
017: * May 6, 1999.
018: *
019: * The Initial Developer of the Original Code is
020: * Netscape Communications Corporation.
021: * Portions created by the Initial Developer are Copyright (C) 1997-2000
022: * the Initial Developer. All Rights Reserved.
023: *
024: * Contributor(s):
025: * Ethan Hugg
026: * Terry Lucas
027: * Milen Nankov
028: * David P. Caldwell <inonit@inonit.com>
029: *
030: * Alternatively, the contents of this file may be used under the terms of
031: * the GNU General Public License Version 2 or later (the "GPL"), in which
032: * case the provisions of the GPL are applicable instead of those above. If
033: * you wish to allow use of your version of this file only under the terms of
034: * the GPL and not to allow others to use your version of this file under the
035: * MPL, indicate your decision by deleting the provisions above and replacing
036: * them with the notice and other provisions required by the GPL. If you do
037: * not delete the provisions above, a recipient may use your version of this
038: * file under either the MPL or the GPL.
039: *
040: * ***** END LICENSE BLOCK ***** */
041:
042: package org.mozilla.javascript.xmlimpl;
043:
044: import org.mozilla.javascript.*;
045: import org.mozilla.javascript.xml.*;
046:
047: class XMLList extends XMLObjectImpl implements Function {
048: static final long serialVersionUID = -4543618751670781135L;
049:
050: private XmlNode.List _annos;
051: private XMLObjectImpl targetObject = null;
052: private XmlNode.QName targetProperty = null;
053:
054: XMLList(XMLLibImpl lib, Scriptable scope, XMLObject prototype) {
055: super (lib, scope, prototype);
056: _annos = new XmlNode.List();
057: }
058:
059: /** @deprecated Will probably end up unnecessary as we move things around */
060: XmlNode.List getNodeList() {
061: return _annos;
062: }
063:
064: // TODO Should be XMLObjectImpl, XMLName?
065: void setTargets(XMLObjectImpl object, XmlNode.QName property) {
066: targetObject = object;
067: targetProperty = property;
068: }
069:
070: /** @deprecated */
071: private XML getXmlFromAnnotation(int index) {
072: return getXML(_annos, index);
073: }
074:
075: XML getXML() {
076: if (length() == 1)
077: return getXmlFromAnnotation(0);
078: return null;
079: }
080:
081: private void internalRemoveFromList(int index) {
082: _annos.remove(index);
083: }
084:
085: void replace(int index, XML xml) {
086: if (index < length()) {
087: XmlNode.List newAnnoList = new XmlNode.List();
088: newAnnoList.add(_annos, 0, index);
089: newAnnoList.add(xml);
090: newAnnoList.add(_annos, index + 1, length());
091: _annos = newAnnoList;
092: }
093: }
094:
095: private void insert(int index, XML xml) {
096: if (index < length()) {
097: XmlNode.List newAnnoList = new XmlNode.List();
098: newAnnoList.add(_annos, 0, index);
099: newAnnoList.add(xml);
100: newAnnoList.add(_annos, index, length());
101: _annos = newAnnoList;
102: }
103: }
104:
105: //
106: //
107: // methods overriding ScriptableObject
108: //
109: //
110:
111: public String getClassName() {
112: return "XMLList";
113: }
114:
115: //
116: //
117: // methods overriding IdScriptableObject
118: //
119: //
120:
121: public Object get(int index, Scriptable start) {
122: //Log("get index: " + index);
123:
124: if (index >= 0 && index < length()) {
125: return getXmlFromAnnotation(index);
126: } else {
127: return Scriptable.NOT_FOUND;
128: }
129: }
130:
131: boolean hasXMLProperty(XMLName xmlName) {
132: boolean result = false;
133:
134: // Has now should return true if the property would have results > 0 or
135: // if it's a method name
136: String name = xmlName.localName();
137: if ((getPropertyList(xmlName).length() > 0)
138: || (getMethod(name) != NOT_FOUND)) {
139: result = true;
140: }
141:
142: return result;
143: }
144:
145: public boolean has(int index, Scriptable start) {
146: return 0 <= index && index < length();
147: }
148:
149: void putXMLProperty(XMLName xmlName, Object value) {
150: //Log("put property: " + name);
151:
152: // Special-case checks for undefined and null
153: if (value == null) {
154: value = "null";
155: } else if (value instanceof Undefined) {
156: value = "undefined";
157: }
158:
159: if (length() > 1) {
160: throw ScriptRuntime
161: .typeError("Assignment to lists with more than one item is not supported");
162: } else if (length() == 0) {
163: // Secret sauce for super-expandos.
164: // We set an element here, and then add ourselves to our target.
165: if (targetObject != null && targetProperty != null
166: && targetProperty.getLocalName() != null) {
167: // Add an empty element with our targetProperty name and then set it.
168: XML xmlValue = newTextElementXML(null, targetProperty,
169: null);
170: addToList(xmlValue);
171:
172: if (xmlName.isAttributeName()) {
173: setAttribute(xmlName, value);
174: } else {
175: XML xml = item(0);
176: xml.putXMLProperty(xmlName, value);
177:
178: // Update the list with the new item at location 0.
179: replace(0, item(0));
180: }
181:
182: // Now add us to our parent
183: XMLName name2 = XMLName.formProperty(targetProperty
184: .getUri(), targetProperty.getLocalName());
185: targetObject.putXMLProperty(name2, this );
186: } else {
187: throw ScriptRuntime
188: .typeError("Assignment to empty XMLList without targets not supported");
189: }
190: } else if (xmlName.isAttributeName()) {
191: setAttribute(xmlName, value);
192: } else {
193: XML xml = item(0);
194: xml.putXMLProperty(xmlName, value);
195:
196: // Update the list with the new item at location 0.
197: replace(0, item(0));
198: }
199: }
200:
201: Object getXMLProperty(XMLName name) {
202: return getPropertyList(name);
203: }
204:
205: private void replaceNode(XML xml, XML with) {
206: xml.replaceWith(with);
207: }
208:
209: public void put(int index, Scriptable start, Object value) {
210: Object parent = Undefined.instance;
211: // Convert text into XML if needed.
212: XMLObject xmlValue;
213:
214: // Special-case checks for undefined and null
215: if (value == null) {
216: value = "null";
217: } else if (value instanceof Undefined) {
218: value = "undefined";
219: }
220:
221: if (value instanceof XMLObject) {
222: xmlValue = (XMLObject) value;
223: } else {
224: if (targetProperty == null) {
225: xmlValue = newXMLFromJs(value.toString());
226: } else {
227: // Note that later in the code, we will use this as an argument to replace(int,value)
228: // So we will be "replacing" this element with itself
229: // There may well be a better way to do this
230: // TODO Find a way to refactor this whole method and simplify it
231: xmlValue = item(index);
232: ((XML) xmlValue).setChildren(value);
233: }
234: }
235:
236: // Find the parent
237: if (index < length()) {
238: parent = item(index).parent();
239: } else {
240: // Appending
241: parent = parent();
242: }
243:
244: if (parent instanceof XML) {
245: // found parent, alter doc
246: XML xmlParent = (XML) parent;
247:
248: if (index < length()) {
249: // We're replacing the the node.
250: XML xmlNode = getXmlFromAnnotation(index);
251:
252: if (xmlValue instanceof XML) {
253: replaceNode(xmlNode, (XML) xmlValue);
254: replace(index, xmlNode);
255: } else if (xmlValue instanceof XMLList) {
256: // Replace the first one, and add the rest on the list.
257: XMLList list = (XMLList) xmlValue;
258:
259: if (list.length() > 0) {
260: int lastIndexAdded = xmlNode.childIndex();
261: replaceNode(xmlNode, list.item(0));
262: replace(index, list.item(0));
263:
264: for (int i = 1; i < list.length(); i++) {
265: xmlParent.insertChildAfter(xmlParent
266: .getXmlChild(lastIndexAdded), list
267: .item(i));
268: lastIndexAdded++;
269: insert(index + i, list.item(i));
270: }
271: }
272: }
273: } else {
274: // Appending
275: xmlParent.appendChild(xmlValue);
276: addToList(xmlParent.getXmlChild(index));
277: }
278: } else {
279: // Don't all have same parent, no underlying doc to alter
280: if (index < length()) {
281: XML xmlNode = getXML(_annos, index);
282:
283: if (xmlValue instanceof XML) {
284: replaceNode(xmlNode, (XML) xmlValue);
285: replace(index, xmlNode);
286: } else if (xmlValue instanceof XMLList) {
287: // Replace the first one, and add the rest on the list.
288: XMLList list = (XMLList) xmlValue;
289:
290: if (list.length() > 0) {
291: replaceNode(xmlNode, list.item(0));
292: replace(index, list.item(0));
293:
294: for (int i = 1; i < list.length(); i++) {
295: insert(index + i, list.item(i));
296: }
297: }
298: }
299: } else {
300: addToList(xmlValue);
301: }
302: }
303: }
304:
305: private XML getXML(XmlNode.List _annos, int index) {
306: if (index >= 0 && index < length()) {
307: return xmlFromNode(_annos.item(index));
308: } else {
309: return null;
310: }
311: }
312:
313: void deleteXMLProperty(XMLName name) {
314: for (int i = 0; i < length(); i++) {
315: XML xml = getXmlFromAnnotation(i);
316:
317: if (xml.isElement()) {
318: xml.deleteXMLProperty(name);
319: }
320: }
321: }
322:
323: public void delete(int index) {
324: if (index >= 0 && index < length()) {
325: XML xml = getXmlFromAnnotation(index);
326:
327: xml.remove();
328:
329: internalRemoveFromList(index);
330: }
331: }
332:
333: public Object[] getIds() {
334: Object enumObjs[];
335:
336: if (isPrototype()) {
337: enumObjs = new Object[0];
338: } else {
339: enumObjs = new Object[length()];
340:
341: for (int i = 0; i < enumObjs.length; i++) {
342: enumObjs[i] = new Integer(i);
343: }
344: }
345:
346: return enumObjs;
347: }
348:
349: public Object[] getIdsForDebug() {
350: return getIds();
351: }
352:
353: // XMLList will remove will delete all items in the list (a set delete) this differs from the XMLList delete operator.
354: void remove() {
355: int nLen = length();
356: for (int i = nLen - 1; i >= 0; i--) {
357: XML xml = getXmlFromAnnotation(i);
358: if (xml != null) {
359: xml.remove();
360: internalRemoveFromList(i);
361: }
362: }
363: }
364:
365: XML item(int index) {
366: return _annos != null ? getXmlFromAnnotation(index)
367: : createEmptyXML();
368: }
369:
370: private void setAttribute(XMLName xmlName, Object value) {
371: for (int i = 0; i < length(); i++) {
372: XML xml = getXmlFromAnnotation(i);
373: xml.setAttribute(xmlName, value);
374: }
375: }
376:
377: void addToList(Object toAdd) {
378: _annos.addToList(toAdd);
379: }
380:
381: //
382: //
383: // Methods from section 12.4.4 in the spec
384: //
385: //
386:
387: XMLList child(int index) {
388: XMLList result = newXMLList();
389:
390: for (int i = 0; i < length(); i++) {
391: result.addToList(getXmlFromAnnotation(i).child(index));
392: }
393:
394: return result;
395: }
396:
397: XMLList child(XMLName xmlName) {
398: XMLList result = newXMLList();
399:
400: for (int i = 0; i < length(); i++) {
401: result.addToList(getXmlFromAnnotation(i).child(xmlName));
402: }
403:
404: return result;
405: }
406:
407: void addMatches(XMLList rv, XMLName name) {
408: for (int i = 0; i < length(); i++) {
409: getXmlFromAnnotation(i).addMatches(rv, name);
410: }
411: }
412:
413: XMLList children() {
414: java.util.Vector v = new java.util.Vector();
415:
416: for (int i = 0; i < length(); i++) {
417: XML xml = getXmlFromAnnotation(i);
418:
419: if (xml != null) {
420: Object o = xml.children();
421: if (o instanceof XMLList) {
422: XMLList childList = (XMLList) o;
423:
424: int cChildren = childList.length();
425: for (int j = 0; j < cChildren; j++) {
426: v.addElement(childList.item(j));
427: }
428: }
429: }
430: }
431:
432: XMLList allChildren = newXMLList();
433: int sz = v.size();
434:
435: for (int i = 0; i < sz; i++) {
436: allChildren.addToList(v.get(i));
437: }
438:
439: return allChildren;
440: }
441:
442: XMLList comments() {
443: XMLList result = newXMLList();
444:
445: for (int i = 0; i < length(); i++) {
446: XML xml = getXmlFromAnnotation(i);
447: result.addToList(xml.comments());
448: }
449:
450: return result;
451: }
452:
453: XMLList elements(XMLName name) {
454: XMLList rv = newXMLList();
455: for (int i = 0; i < length(); i++) {
456: XML xml = getXmlFromAnnotation(i);
457: rv.addToList(xml.elements(name));
458: }
459: return rv;
460: }
461:
462: boolean contains(Object xml) {
463: boolean result = false;
464:
465: for (int i = 0; i < length(); i++) {
466: XML member = getXmlFromAnnotation(i);
467:
468: if (member.equivalentXml(xml)) {
469: result = true;
470: break;
471: }
472: }
473:
474: return result;
475: }
476:
477: XMLObjectImpl copy() {
478: XMLList result = newXMLList();
479:
480: for (int i = 0; i < length(); i++) {
481: XML xml = getXmlFromAnnotation(i);
482: result.addToList(xml.copy());
483: }
484:
485: return result;
486: }
487:
488: boolean hasOwnProperty(XMLName xmlName) {
489: if (isPrototype()) {
490: String property = xmlName.localName();
491: return (findPrototypeId(property) != 0);
492: } else {
493: return (getPropertyList(xmlName).length() > 0);
494: }
495: }
496:
497: boolean hasComplexContent() {
498: boolean complexContent;
499: int length = length();
500:
501: if (length == 0) {
502: complexContent = false;
503: } else if (length == 1) {
504: complexContent = getXmlFromAnnotation(0)
505: .hasComplexContent();
506: } else {
507: complexContent = false;
508:
509: for (int i = 0; i < length; i++) {
510: XML nextElement = getXmlFromAnnotation(i);
511: if (nextElement.isElement()) {
512: complexContent = true;
513: break;
514: }
515: }
516: }
517:
518: return complexContent;
519: }
520:
521: boolean hasSimpleContent() {
522: if (length() == 0) {
523: return true;
524: } else if (length() == 1) {
525: return getXmlFromAnnotation(0).hasSimpleContent();
526: } else {
527: for (int i = 0; i < length(); i++) {
528: XML nextElement = getXmlFromAnnotation(i);
529: if (nextElement.isElement()) {
530: return false;
531: }
532: }
533: return true;
534: }
535: }
536:
537: int length() {
538: int result = 0;
539:
540: if (_annos != null) {
541: result = _annos.length();
542: }
543:
544: return result;
545: }
546:
547: void normalize() {
548: for (int i = 0; i < length(); i++) {
549: getXmlFromAnnotation(i).normalize();
550: }
551: }
552:
553: /**
554: * If list is empty, return undefined, if elements have different parents return undefined,
555: * If they all have the same parent, return that parent
556: */
557: Object parent() {
558: if (length() == 0)
559: return Undefined.instance;
560:
561: XML candidateParent = null;
562:
563: for (int i = 0; i < length(); i++) {
564: Object currParent = getXmlFromAnnotation(i).parent();
565: if (!(currParent instanceof XML))
566: return Undefined.instance;
567: XML xml = (XML) currParent;
568: if (i == 0) {
569: // Set the first for the rest to compare to.
570: candidateParent = xml;
571: } else {
572: if (candidateParent.is(xml)) {
573: // keep looking
574: } else {
575: return Undefined.instance;
576: }
577: }
578: }
579: return candidateParent;
580: }
581:
582: XMLList processingInstructions(XMLName xmlName) {
583: XMLList result = newXMLList();
584:
585: for (int i = 0; i < length(); i++) {
586: XML xml = getXmlFromAnnotation(i);
587:
588: result.addToList(xml.processingInstructions(xmlName));
589: }
590:
591: return result;
592: }
593:
594: boolean propertyIsEnumerable(Object name) {
595: long index;
596: if (name instanceof Integer) {
597: index = ((Integer) name).intValue();
598: } else if (name instanceof Number) {
599: double x = ((Number) name).doubleValue();
600: index = (long) x;
601: if (index != x) {
602: return false;
603: }
604: if (index == 0 && 1.0 / x < 0) {
605: // Negative 0
606: return false;
607: }
608: } else {
609: String s = ScriptRuntime.toString(name);
610: index = ScriptRuntime.testUint32String(s);
611: }
612: return (0 <= index && index < length());
613: }
614:
615: XMLList text() {
616: XMLList result = newXMLList();
617:
618: for (int i = 0; i < length(); i++) {
619: result.addToList(getXmlFromAnnotation(i).text());
620: }
621:
622: return result;
623: }
624:
625: public String toString() {
626: // ECMA357 10.1.2
627: if (hasSimpleContent()) {
628: StringBuffer sb = new StringBuffer();
629:
630: for (int i = 0; i < length(); i++) {
631: XML next = getXmlFromAnnotation(i);
632: if (next.isComment() || next.isProcessingInstruction()) {
633: // do nothing
634: } else {
635: sb.append(next.toString());
636: }
637: }
638:
639: return sb.toString();
640: } else {
641: return toXMLString();
642: }
643: }
644:
645: String toXMLString() {
646: // See ECMA 10.2.1
647: StringBuffer sb = new StringBuffer();
648:
649: for (int i = 0; i < length(); i++) {
650: if (getProcessor().isPrettyPrinting() && i != 0) {
651: sb.append('\n');
652: }
653: sb.append(getXmlFromAnnotation(i).toXMLString());
654: }
655: return sb.toString();
656: }
657:
658: Object valueOf() {
659: return this ;
660: }
661:
662: //
663: // Other public Functions from XMLObject
664: //
665:
666: boolean equivalentXml(Object target) {
667: boolean result = false;
668:
669: // Zero length list should equate to undefined
670: if (target instanceof Undefined && length() == 0) {
671: result = true;
672: } else if (length() == 1) {
673: result = getXmlFromAnnotation(0).equivalentXml(target);
674: } else if (target instanceof XMLList) {
675: XMLList otherList = (XMLList) target;
676:
677: if (otherList.length() == length()) {
678: result = true;
679:
680: for (int i = 0; i < length(); i++) {
681: if (!getXmlFromAnnotation(i).equivalentXml(
682: otherList.getXmlFromAnnotation(i))) {
683: result = false;
684: break;
685: }
686: }
687: }
688: }
689:
690: return result;
691: }
692:
693: private XMLList getPropertyList(XMLName name) {
694: XMLList propertyList = newXMLList();
695: XmlNode.QName qname = null;
696:
697: if (!name.isDescendants() && !name.isAttributeName()) {
698: // Only set the targetProperty if this is a regular child get
699: // and not a descendant or attribute get
700: qname = name.toQname();
701: }
702:
703: propertyList.setTargets(this , qname);
704:
705: for (int i = 0; i < length(); i++) {
706: propertyList.addToList(getXmlFromAnnotation(i)
707: .getPropertyList(name));
708: }
709:
710: return propertyList;
711: }
712:
713: private Object applyOrCall(boolean isApply, Context cx,
714: Scriptable scope, Scriptable this Obj, Object[] args) {
715: String methodName = isApply ? "apply" : "call";
716: if (!(this Obj instanceof XMLList)
717: || ((XMLList) this Obj).targetProperty == null)
718: throw ScriptRuntime.typeError1("msg.isnt.function",
719: methodName);
720:
721: return ScriptRuntime.applyOrCall(isApply, cx, scope, this Obj,
722: args);
723: }
724:
725: protected Object jsConstructor(Context cx, boolean inNewExpr,
726: Object[] args) {
727: if (args.length == 0) {
728: return newXMLList();
729: } else {
730: Object arg0 = args[0];
731: if (!inNewExpr && arg0 instanceof XMLList) {
732: // XMLList(XMLList) returns the same object.
733: return arg0;
734: }
735: return newXMLListFrom(arg0);
736: }
737: }
738:
739: /**
740: * See ECMA 357, 11_2_2_1, Semantics, 3_e.
741: */
742: public Scriptable getExtraMethodSource(Context cx) {
743: if (length() == 1) {
744: return getXmlFromAnnotation(0);
745: }
746: return null;
747: }
748:
749: public Object call(Context cx, Scriptable scope,
750: Scriptable this Obj, Object[] args) {
751: // This XMLList is being called as a Function.
752: // Let's find the real Function object.
753: if (targetProperty == null)
754: throw ScriptRuntime.notFunctionError(this );
755:
756: String methodName = targetProperty.getLocalName();
757:
758: boolean isApply = methodName.equals("apply");
759: if (isApply || methodName.equals("call"))
760: return applyOrCall(isApply, cx, scope, this Obj, args);
761:
762: Callable method = ScriptRuntime.getElemFunctionAndThis(this ,
763: methodName, cx);
764: // Call lastStoredScriptable to clear stored thisObj
765: // but ignore the result as the method should use the supplied
766: // thisObj, not one from redirected call
767: ScriptRuntime.lastStoredScriptable(cx);
768: return method.call(cx, scope, this Obj, args);
769: }
770:
771: public Scriptable construct(Context cx, Scriptable scope,
772: Object[] args) {
773: throw ScriptRuntime.typeError1("msg.not.ctor", "XMLList");
774: }
775: }
|