
libjava/ChangeLog: 2008-10-21 Andrew John Hughes <gnu_andrew@member.fsf.org> * sources.am, Makfile.in: Regenerate. 2008-10-17 Matthias Klose <doko@ubuntu.com> * configure.ac: Fix bashisms. * configure: Regenerate. 2008-10-15 Matthias Klose <doko@ubuntu.com> * configure.ac: Disable build of gjdoc, if configured without --with-antlr-jar or if no antlr.jar found. * configure: Regenerate. 2008-10-09 Andrew John Hughes <gnu_andrew@member.fsf.org> * classpath/configure.ac, * classpath/m4/ac_prog_antlr.m4, * classpath/m4/ac_prog_java.m4, * classpath/tools/Makefile.am: Ported --regen-gjdoc-parser patch and cantlr support from GNU Classpath. 2008-10-06 Andrew Haley <aph@redhat.com> * java/lang/Thread.java (Thread): Always create the ThreadLocalMap when creating a thread. (getThreadLocals) Don't lazily create the ThreadLocalMap. 2008-09-28 Andrew John Hughes <gnu_andrew@member.fsf.org> * classpath/java/lang/ThreadLocalMap.java, * java/lang/ThreadLocalMap$Entry.h, * java/lang/ThreadLocalMap.h, * lib/java/lang/ThreadLocalMap.class, * lib/java/lang/ThreadLocalMap$Entry.class: Add the new files for the ThreadLocal patch. 2008-09-28 Andrew John Hughes <gnu_andrew@member.fsf.org> * classpath/ChangeLog, * classpath/java/lang/InheritableThreadLocal.java, * classpath/java/lang/Thread.java, * classpath/java/lang/ThreadLocal.java: Merge Daniel Frampton's ThreadLocal patch. * gcj/javaprims.h: Updated. * java/lang/Thread.h: Regenerated. * java/lang/Thread.java: Replace WeakIdentityHashMap with ThreadLocalMap. (getThreadLocals()): Likewise. * java/lang/ThreadLocal.h: Regenerated. * java/lang/ThreadLocal.java: (computeNextHash()): New method. (ThreadLocal()): Initialise fastHash. (internalGet()): Updated to match Classpath's get(). (internalSet(Object)): Likewise for set(Object). (internalRemove()): Likewise for remove(). 2008-09-25 Andrew John Hughes <gnu_andrew@member.fsf.org> * classpath/configure, * classpath/configure.ac: Resynchronise with Classpath's configure. * classpath/examples/Makefile.in: Add equivalent support for building as in tools/Makefile.in. * classpath/java/nio/Buffer.java, * classpath/java/nio/ByteBuffer.java, * classpath/java/nio/ByteBufferImpl.java, * classpath/java/nio/CharBuffer.java, * classpath/java/nio/CharBufferImpl.java, * classpath/java/nio/CharSequenceBuffer.java, * classpath/java/nio/CharViewBufferImpl.java, * classpath/java/nio/DirectByteBufferImpl.java, * classpath/java/nio/DoubleBuffer.java, * classpath/java/nio/DoubleBufferImpl.java, * classpath/java/nio/DoubleViewBufferImpl.java, * classpath/java/nio/FloatBuffer.java, * classpath/java/nio/FloatBufferImpl.java, * classpath/java/nio/FloatViewBufferImpl.java, * classpath/java/nio/IntBuffer.java, * classpath/java/nio/IntBufferImpl.java, * classpath/java/nio/IntViewBufferImpl.java, * classpath/java/nio/LongBuffer.java, * classpath/java/nio/LongBufferImpl.java, * classpath/java/nio/LongViewBufferImpl.java, * classpath/java/nio/MappedByteBuffer.java, * classpath/java/nio/MappedByteBufferImpl.java, * classpath/java/nio/ShortBuffer.java, * classpath/java/nio/ShortBufferImpl.java, * classpath/java/nio/ShortViewBufferImpl.java: Replace use of gnu.classpath.Pointer with gnu.gcj.RawData, and fix some formatting issues. * classpath/tools/gnu/classpath/tools/gjdoc/expr/JavaLexer.java, * classpath/tools/gnu/classpath/tools/gjdoc/expr/JavaLexer.smap, * classpath/tools/gnu/classpath/tools/gjdoc/expr/JavaRecognizer.java, * classpath/tools/gnu/classpath/tools/gjdoc/expr/JavaRecognizer.smap, * classpath/tools/gnu/classpath/tools/gjdoc/expr/JavaTokenTypes.java, * classpath/tools/gnu/classpath/tools/gjdoc/expr/JavaTokenTypes.txt: Regenerated (later version of antlr). * java/nio/Buffer.h: Regenerated. * java/nio/Buffer.java: Ported changes from Classpath. * java/nio/ByteBuffer.h, * java/nio/CharBuffer.h: Regenerated. * java/nio/DirectByteBufferImpl.java: Ported changes from Classpath. * java/nio/DoubleBuffer.h, * java/nio/FloatBuffer.h, * java/nio/IntBuffer.h, * java/nio/LongBuffer.h, * java/nio/MappedByteBuffer.h, * java/nio/MappedByteBufferImpl.h: Regenerated. * java/nio/MappedByteBufferImpl.java: Ported changes from Classpath. * java/nio/ShortBuffer.h: Regenerated. 2008-09-24 Matthias Klose <doko@ubuntu.com> * configure.ac: Search for antlr.jar, if not configured. * configure: Regenerate. 2008-09-24 Matthias Klose <doko@ubuntu.com> * Makefile.am: Build a gjdoc binary, if enabled. * configure.ac: Add options --disable-gjdoc, --with-antlr-jar=file. * Makefile.in, */Makefile.in, configure: Regenerate. 2008-09-22 Andrew Haley <aph@redhat.com> * java/lang/String.java (toString(char[], int, int)): New method. 2008-09-14 Matthias Klose <doko@ubuntu.com> Import GNU Classpath (libgcj-import-20080914). * Regenerate class and header files. * Regenerate auto* files. * configure.ac: Don't pass --disable-gjdoc to classpath. * sources.am: Regenerated. * HACKING: Mention to build gjdoc in maintainer builds. * gnu/classpath/Configuration.java: Update classpath version. * gcj/javaprims.h: Update. 2008-09-08 Andrew John Hughes <gnu_andrew@member.fsf.org> * Makefile.am: Replace natStringBuffer.cc and natStringBuilder.cc with natAbstractStringBuffer.cc. * Makefile.in: Regenerated. * java/lang/AbstractStringBuffer.java: (append(int)): Made native. (regionMatches(int,String)): Likewise. * java/lang/StringBuffer.h: Regenerated. * java/lang/StringBuffer.java: Remerged with GNU Classpath. * java/lang/StringBuilder.h: Regenerated. * java/lang/StringBuilder.java: Remerged with GNU Classpath. * java/lang/natAbstractStringBuffer.cc: Provide common native methods for StringBuffer and StringBuilder. * java/lang/natStringBuffer.cc, * java/lang/natStringBuilder.cc: Removed. 2008-09-04 Andrew John Hughes <gnu_andrew@member.fsf.org> * Makefile.in, * classpath/configure: Regenerated. * gnu/gcj/util/natDebug.cc, * gnu/gcj/xlib/natColormap.cc, * gnu/gcj/xlib/natDisplay.cc, * gnu/gcj/xlib/natDrawable.cc, * gnu/gcj/xlib/natFont.cc, * gnu/gcj/xlib/natWMSizeHints.cc, * gnu/gcj/xlib/natWindow.cc, * gnu/gcj/xlib/natXImage.cc: Add :: prefix to namespaces. * java/io/CharArrayWriter.h, * java/lang/StringBuffer.h: Regenerated using patched gjavah. * java/lang/natStringBuffer.cc: Fix naming of append(jint). * java/sql/Timestamp.h: Regenerated using patched gjavah. * jni.cc: Rename p to functions to match change in GNU Classpath. * scripts/makemake.tcl: Switch gnu.java.math to BC compilation. * sources.am: Regenerated. 2008-08-21 Andrew John Hughes <gnu_andrew@member.fsf.org> * Makefile.in: Updated location of Configuration.java. * classpath/lib/gnu/java/locale/LocaleData.class: Regenerated. 2008-08-18 Andrew John Hughes <gnu_andrew@member.fsf.org> * Makefile.in: Updated with new Java files. * classpath/configure: Regenerated. * classpath/tools/Makefile.am: Add missing use of GJDOC_EX so --disable-gjdoc works. * classpath/tools/Makefile.in: Regenerated. 2008-08-15 Matthias Klose <doko@ubuntu.com> Import GNU Classpath (libgcj-import-20080811). * Regenerate class and header files. * Regenerate auto* files. * configure.ac: Don't pass --with-fastjar to classpath, substitute new dummy value in classpath/gnu/classpath/Configuration.java.in, pass --disable-gjdoc to classpath. * scripts/makemake.tcl: * sources.am: Regenerated. * java/lang/AbstractStringBuffer.java, gnu/java/lang/VMCPStringBuilder.java: New, copied from classpath, use System instead of VMSystem. * java/lang/StringBuffer.java: Merge from classpath. * java/lang/ClassLoader.java: Merge from classpath. * gcj/javaprims.h: Update class definitions, remove _Jv_jobjectRefType, jobjectRefType definitions. libjava/classpath/ChangeLog.gcj: 2008-10-21 Matthias Klose <doko@ubuntu.com> * classpath/tools/gnu/classpath/tools/gjdoc/expr/Java*: Move from ... * classpath/tools/generated/gnu/classpath/tools/gjdoc/expr/ ... here. * Update .class files. 2008-10-21 Andrew John Hughes <gnu_andrew@member.fsf.org> * tools/Makefile.am: Always generate parser in the srcdir. 2008-10-21 Matthias Klose <doko@ubuntu.com> * doc/Makefile.am (MAINTAINERCLEANFILES): Add gjdoc.1. * doc/Makefile.in: Regenerate. 2008-10-20 Matthias Klose <doko@ubuntu.com> * configure.ac: Don't check for working java, if not configured with --enable-java-maintainer-mode. * configure: Regenerate. 2008-10-19 Matthias Klose <doko@ubuntu.com> * m4/ac_prog_java.m4: Revert previous change. * m4/ac_prog_javac.m4: Apply it here. * configure: Regenerate. 2008-10-19 Matthias Klose <doko@ubuntu.com> * m4/ac_prog_javac.m4: Don't check for working javac, if not configured with --enable-java-maintainer-mode. * configure: Regenerate. * Makefile.in, */Makefile.in: Regenerate. 2008-09-30 Matthias Klose <doko@ubuntu.com> * m4/ac_prog_antlr.m4: Check for cantlr binary as well. 2008-09-29 Matthias Klose <doko@ubuntu.com> * m4/ac_prog_antlr.m4: Check for antlr binary as well. 2008-09-28 Matthias Klose <doko@ubuntu.com> * PR libgcj/37636. Revert: 2008-02-20 Matthias Klose <doko@ubuntu.com> * tools/Makefile.am ($(TOOLS_ZIP)): Revert part of previous change, Do copy resource files in JAVA_MAINTAINER_MODE only. * tools/Makefile.in: Regenerate. 2008-09-14 Matthias Klose <doko@ubuntu.com> * m4/ac_prog_javac_works.m4, m4/ac_prog_javac.m4, m4/acinclude.m4: Revert local changes. * m4/ac_prog_antlr.m4: Check for an runantlr binary. * tools/Makefile.am, lib/Makefile.am: Revert local changes (JCOMPILER). * tools/Makefile.am: Remove USE_JAVAC_FLAGS, pass ANTLR_JAR in GLIBJ_CLASSPATH. 2008-09-14 Matthias Klose <doko@ubuntu.com> Revert: Daniel Frampton <zyridium at zyridium.net> * AUTHORS: Added. * java/lang/InheritableThreadLocal.java, * java/lang/Thread.java, * java/lang/ThreadLocal.java: Modified to use java.lang.ThreadLocalMap. * java/lang/ThreadLocalMap.java: New cheaper ThreadLocal-specific WeakHashMap. 2008-08-15 Matthias Klose <doko@ubuntu.com> * m4/acinclude.m4 (CLASSPATH_JAVAC_MEM_CHECK): Remove unknown args for javac. libjava/classpath/ChangeLog: 2008-10-20 Andrew John Hughes <gnu_andrew@member.fsf.org> * m4/ac_prog_antlr.m4: Remove redundant checks. * tools/Makefile.am: Use gjdoc_gendir when calling antlr. 2008-10-15 Andrew John Hughes <gnu_andrew@member.fsf.org> * configure.ac: Remove superfluous AC_PROG_JAVA call. 2008-10-06 Andrew John Hughes <gnu_andrew@member.fsf.org> * m4/ac_prog_antlr: Check for cantlr as well. * tools/Makefile.am: Only build GJDoc parser when both CREATE_GJDOC and CREATE_GJDOC_PARSER are on. 2008-10-02 Andrew John Hughes <gnu_andrew@member.fsf.org> * configure.ac: Add regen-gjdoc-parser option, and separate antlr tests. * m4/ac_prog_antlr.m4: Turn single test into AC_LIB_ANTLR and AC_PROG_ANTLR. * m4/ac_prog_java.m4: Quote tests. * tools/Makefile.am: Support CREATE_GJDOC_PARSER option. 2008-09-14 Andrew John Hughes <gnu_andrew@member.fsf.org> * examples/Makefile.am: Check lib directly as well as glibj.zip for boot classes. * m4/acinclude.m4: Only require the class files to be built to allow the tools and examples to be built, not the installation of glibj.zip. * tools/Makefile.am: Check lib directly as well as glibj.zip for boot classes. 2008-09-13 Andrew John Hughes <gnu_andrew@member.fsf.org> * examples/Makefile.am, * lib/Makefile.am: Add GCJ rules. * m4/ac_prog_javac.m4: Check whether JAVAC is gcj. * m4/ac_prog_javac_works.m4: Add GCJ rules. * m4/acinclude.m4: Don't bother checking for -J if using GCJ. * tools/Makefile.am: Add GCJ rules. 2007-08-23 Daniel Frampton <zyridium@zyridium.net> * AUTHORS: Added. * java/lang/InheritableThreadLocal.java, * java/lang/Thread.java, * java/lang/ThreadLocal.java: Modified to use java.lang.ThreadLocalMap. * java/lang/ThreadLocalMap.java: New cheaper ThreadLocal-specific WeakHashMap. 2008-02-07 Ian Rogers <ian.rogers@manchester.ac.uk> * java/util/zip/ZipEntry.java: Use byte fields instead of integer fields, store the time as well as the DOS time and don't retain a global Calendar instance. (setDOSTime(int)): Set KNOWN_DOSTIME instead of KNOWN_TIME, and unset KNOWN_TIME. (getDOSTime()): Compute DOS time from UNIX time only when needed. (clone()): Provide cloning via the ZipEntry constructor where possible. (setTime(long)): Don't compute DOS time at this point. (getCalendar()): Removed. 2008-09-09 Andrew John Hughes <gnu_andrew@member.fsf.org> * tools/gnu/classpath/tools/getopt/Parser.java: (setHeader(String)): Make synchronized. (setFooter(String)): Likewise. * tools/gnu/classpath/tools/rmic/SourceGiopRmicCompiler.java, (reset()): Make synchronized. (name(Class)): Likewise. 2008-09-04 Robert Schuster <robertschuster@fsfe.org> * gnu/java/nio/charset/ByteDecodeLoopHelper: (arrayDecodeLoop): Added new break label, escape to that label. * gnu/java/nio/charset/ByteEncodeLoopHelper: (arrayDecodeLoop): Added new break label, escape to that label. 2008-09-04 Robert Schuster <robertschuster@fsfe.org> * java/text/DecimalFormat.java: (scanFix): Use 'i + 1' when looking at following character. (scanNegativePattern): Dito. 2008-09-02 Andrew John Hughes <gnu_andrew@member.fsf.org> * tools/gnu/classpath/tools/javah/ClassWrapper.java: (makeVtable()): Populate methodNameMap. (printMethods(CniPrintStream)): Always use pre-populated methodNameMap for bridge targets. 2008-09-01 Mario Torre <neugens@aicas.com> * gnu/java/awt/peer/x/XImage.java (XImageProducer): remove @Override annotation to allow compilation on javac < 1.6 and ecj < 3.4. 2008-09-01 Mario Torre <neugens@aicas.com> * gnu/java/awt/peer/x/XGraphicsDevice.java (getDisplay): fix to support new Escher API. * gnu/java/awt/peer/x/XImage.java (getSource): method implemented. * gnu/java/awt/peer/x/XImage.java (XImageProducer): implement ImageProducer for getSource. 2008-09-01 Andrew John Hughes <gnu_andrew@member.fsf.org> * gnu/java/util/regex/BacktrackStack.java, * gnu/java/util/regex/CharIndexed.java, * gnu/java/util/regex/CharIndexedCharArray.java, * gnu/java/util/regex/CharIndexedCharSequence.java, * gnu/java/util/regex/CharIndexedInputStream.java, * gnu/java/util/regex/CharIndexedString.java, * gnu/java/util/regex/CharIndexedStringBuffer.java, * gnu/java/util/regex/RE.java, * gnu/java/util/regex/REException.java, * gnu/java/util/regex/REFilterInputStream.java, * gnu/java/util/regex/REMatch.java, * gnu/java/util/regex/REMatchEnumeration.java, * gnu/java/util/regex/RESyntax.java, * gnu/java/util/regex/REToken.java, * gnu/java/util/regex/RETokenAny.java, * gnu/java/util/regex/RETokenBackRef.java, * gnu/java/util/regex/RETokenChar.java, * gnu/java/util/regex/RETokenEnd.java, * gnu/java/util/regex/RETokenEndOfPreviousMatch.java, * gnu/java/util/regex/RETokenEndSub.java, * gnu/java/util/regex/RETokenIndependent.java, * gnu/java/util/regex/RETokenLookAhead.java, * gnu/java/util/regex/RETokenLookBehind.java, * gnu/java/util/regex/RETokenNamedProperty.java, * gnu/java/util/regex/RETokenOneOf.java, * gnu/java/util/regex/RETokenPOSIX.java, * gnu/java/util/regex/RETokenRange.java, * gnu/java/util/regex/RETokenRepeated.java, * gnu/java/util/regex/RETokenStart.java, * gnu/java/util/regex/RETokenWordBoundary.java, * gnu/java/util/regex/UncheckedRE.java: Fix indentation. 2008-09-01 Andrew John Hughes <gnu_andrew@member.fsf.org> * gnu/java/util/regex/RETokenStart.java: (getMaximumLength()): Add Override annotation. (matchThis(CharIndexed, REMatch)): Likewise. (returnsFixedLengthMatches()): Renamed from returnsFixedLengthmatches and added Override annotation. (findFixedLengthMatches(CharIndexed,REMatch,int)): Add Override annotation. (dump(CPStringBuilder)): Likewise. * gnu/javax/print/ipp/IppRequest.java: (RequestWriter.writeOperationAttributes(AttributeSet)): Throw exception, don't just create and drop it. * javax/management/MBeanServerPermission.java: (MBeanServerPermissionCollection.add(Permission)): Compare against individual Strings not the entire array, and store the result of replace. * javax/swing/text/html/StyleSheet.java: (setBaseFontSize(size)): Store result of trim(). 2008-09-01 Andrew John Hughes <gnu_andrew@member.fsf.org> * javax/tools/FileObject.java: (openReader(boolean)): Document new parameter. 2008-03-27 Michael Franz <mvfranz@gmail.com> PR classpath/35690: * javax/tools/FileObject.java: (toUri()): Fix case from toURI. (openReader(boolean)): Add missing boolean argument. 2008-08-26 Andrew John Hughes <gnu_andrew@member.fsf.org> PR classpath/35487: * gnu/javax/management/Server.java: (beans): Change to ConcurrentHashMap. (defaultDomain): Make final. (outer): Likewise. (LazyListenersHolder): Added to wrap listeners, also now a ConcurrentHashMap, providing lazy initialisation safely. (sequenceNumber): Documented. (getBean(ObjectName)): Remove redundant cast. (addNotificationListener(ObjectName,NotificationListener, NotificationFilter,Object)): Remove map initialisation and use holder. (getObjectInstance(ObjectName)): Remove redundant cast. (registerMBean(Object,ObjectName)): Add bean atomically. (removeNotificationListener(ObjectName,NotificationListener)): Simplified. (removeNotificationListener(ObjectName,NotificationListener, NotificationFilter,Object)): Likewise. (notify(ObjectName,String)): Documented. 2008-08-26 Andrew John Hughes <gnu_andrew@member.fsf.org> * gnu/javax/management/Server.java: Genericised. 2008-08-26 Andrew John Hughes <gnu_andrew@member.fsf.org> * gnu/javax/management/Translator.java: Genericised. 2008-08-26 Andrew John Hughes <gnu_andrew@member.fsf.org> * javax/management/DefaultLoaderRepository.java, * javax/management/JMX.java, * javax/management/MBeanAttributeInfo.java, * javax/management/MBeanConstructorInfo.java, * javax/management/MBeanOperationInfo.java, * javax/management/MBeanServerDelegate.java: Fix warnings due to generics. 2008-08-25 Andrew John Hughes <gnu_andrew@member.fsf.org> * javax/management/MBeanPermission.java, * javax/management/MBeanServerDelegate.java, * javax/management/MBeanServerFactory.java, * javax/management/MBeanServerInvocationHandler.java, * javax/management/MBeanServerPermission.java: Fix warnings due to use of non-generic collections. 2008-08-25 Mario Torre <neugens@aicas.com> * gnu/javax/rmi/CORBA/RmiUtilities.java (readValue): check if sender is null to avoid NPE. 2008-08-22 Mario Torre <neugens@aicas.com> * gnu/CORBA/OrbFunctional.java (set_parameters): Fix NullPointerException checking when param is null. 2008-08-23 Andrew John Hughes <gnu_andrew@member.fsf.org> * java/util/regex/Matcher.java: (reset()): Reset append position so we don't try and append to the end of the old input. 2008-08-22 Andrew John Hughes <gnu_andrew@member.fsf.org> PR classpath/32028: * m4/acinclude.m4: Also allow versions of GJDoc from 0.8* on, as CVS is 0.8.0-pre. 2008-08-21 Andrew John Hughes <gnu_andrew@member.fsf.org> PR classpath/32028: * m4/acinclude.m4: (CLASSPATH_WITH_GJDOC): Ensure version 0.7.9 is being used. 2008-08-20 Andrew John Hughes <gnu_andrew@member.fsf.org> * tools/Makefile.am: Add taglets subdirectory to list of excluded paths when GJDoc is not compiled. 2008-08-19 David P Grove <groved@us.ibm.com> * scripts/check_jni_methods.sh.in: Fix build issue on AIX by splitting generation of method list. 2008-08-18 Andrew John Hughes <gnu_andrew@member.fsf.org> * native/jni/gstreamer-peer/gst_native_pipeline.c: (get_free_space(int)): Use #else not #elif when there is no condition. 2008-08-17 Andrew John Hughes <gnu_andrew@member.fsf.org> PR classpath/31895: * java/text/DecimalFormat.java: (setCurrency(Currency)): Update prefixes and suffixes when currency changes. * java/text/DecimalFormatSymbols.java: (DecimalFormatSymbols(Locale)): Set locale earlier so it can be used by setCurrency(Currency). (setCurrency(Currency)): Set the symbol correctly using the locale of the instance. * java/util/Currency.java: Throw error instead of just printing a message. 2008-08-17 Andrew John Hughes <gnu_andrew@member.fsf.org> * javax/activation/ActivationDataFlavor.java: Suppress warnings from public API. (mimeType): Made final. (representationClass): Added generic type and made final. (normalizeMimeTypeParameter(String,String)): Use CPStringBuilder. * javax/activation/CommandInfo.java: (verb): Made final. (className): Made final. * javax/activation/DataHandler.java: (dataSource): Made final. * javax/activation/FileDataSource.java: (file): Made final. * javax/activation/MailcapCommandMap.java: Use generics on collections and CPStringBuilder instead of StringBuffer. * javax/activation/MimeType.java: (toString()): Use CPStringBuilder. (getBaseType()): Likewise. * javax/activation/MimeTypeParameterList.java: Use generics on collections and CPStringBuilder instead of StringBuffer. * javax/activation/MimeTypeParseException.java: (MimeTypeParseException(String,String)): Use CPStringBuilder. * javax/activation/MimetypesFileTypeMap.java: Use generics on collections and CPStringBuilder instead of StringBuffer. * javax/activation/URLDataSource.java: (url): Made final. 2008-08-17 Andrew John Hughes <gnu_andrew@member.fsf.org> * gnu/javax/activation/viewers/ImageViewer.java, * gnu/javax/activation/viewers/TextEditor.java, * gnu/javax/activation/viewers/TextViewer.java, * javax/activation/ActivationDataFlavor.java, * javax/activation/CommandInfo.java, * javax/activation/CommandMap.java, * javax/activation/CommandObject.java, * javax/activation/DataContentHandler.java, * javax/activation/DataContentHandlerFactory.java, * javax/activation/DataHandler.java, * javax/activation/DataHandlerDataSource.java, * javax/activation/DataSource.java, * javax/activation/DataSourceDataContentHandler.java, * javax/activation/FileDataSource.java, * javax/activation/FileTypeMap.java, * javax/activation/MailcapCommandMap.java, * javax/activation/MimeType.java, * javax/activation/MimeTypeParameterList.java, * javax/activation/MimeTypeParseException.java, * javax/activation/MimetypesFileTypeMap.java, * javax/activation/ObjectDataContentHandler.java, * javax/activation/URLDataSource.java, * javax/activation/UnsupportedDataTypeException.java, * javax/activation/package.html, * resource/META-INF/mailcap.default, * resource/META-INF/mimetypes.default: Import GNU JAF CVS as of 17/08/2008. 2006-04-25 Archit Shah <ashah@redhat.com> * javax/activation/MimeTypeParameterList.java: Insert ';' separator before parameter list. 2005-06-29 Xavier Poinsard <xpoinsard@openpricer.com> * javax/activation/ObjectDataContentHandler.java: Fixed typo. 2005-05-28 Chris Burdess <dog@bluezoo.org> * javax/activation/CommandMap.java, * javax/activation/MailcapCommandMap.java: Updated to JAF 1.1. 2004-06-09 Chris Burdess <dog@bluezoo.org> * javax/activation/MailcapCommandMap.java: Fixed bug whereby x-java prefix was not attempted. 2008-08-17 Andrew John Hughes <gnu_andrew@member.fsf.org> * AUTHORS: Added Laszlo. 2008-04-20 Andrew John Hughes <gnu_andrew@member.fsf.org> PR classpath/30436: * java/util/Scanner.java: Fix package to be java.util and correct indentation. 2007-07-25 Laszlo Andras Hernadi <e0327023@student.tuwien.ac.at> PR classpath/30436: * java/util/Scanner.java: Initial implementation. 2008-08-17 Andrew John Hughes <gnu_andrew@member.fsf.org> * java/util/regex/Matcher.java: (toMatchResult()): Implemented. 2008-08-13 Joshua Sumali <jsumali@redhat.com> * doc/Makefile.am (gjdoc.pod): Generate gjdoc pod from cp-tools.texinfo instead of invoke.texi. Remove invoke.texi from EXTRA_DIST. * doc/invoke.texi: Removed and merged into ... * doc/cp-tools.texinfo: Here 2008-08-12 Robert Schuster <robertschuster@fsfe.org> * native/jni/java-net/local.c (local_bind): Removed fprintf call, fixed access outside of array bounds. From-SVN: r141271
2223 lines
62 KiB
Java
2223 lines
62 KiB
Java
/* DomNode.java --
|
|
Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Classpath.
|
|
|
|
GNU Classpath is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
GNU Classpath is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GNU Classpath; see the file COPYING. If not, write to the
|
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301 USA.
|
|
|
|
Linking this library statically or dynamically with other modules is
|
|
making a combined work based on this library. Thus, the terms and
|
|
conditions of the GNU General Public License cover the whole
|
|
combination.
|
|
|
|
As a special exception, the copyright holders of this library give you
|
|
permission to link this library with independent modules to produce an
|
|
executable, regardless of the license terms of these independent
|
|
modules, and to copy and distribute the resulting executable under
|
|
terms of your choice, provided that you also meet, for each linked
|
|
independent module, the terms and conditions of the license of that
|
|
module. An independent module is a module which is not derived from
|
|
or based on this library. If you modify this library, you may extend
|
|
this exception to your version of the library, but you are not
|
|
obligated to do so. If you do not wish to do so, delete this
|
|
exception statement from your version. */
|
|
|
|
package gnu.xml.dom;
|
|
|
|
import gnu.java.lang.CPStringBuilder;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
|
|
import org.w3c.dom.Document;
|
|
import org.w3c.dom.DOMException;
|
|
import org.w3c.dom.DOMImplementation;
|
|
import org.w3c.dom.NamedNodeMap;
|
|
import org.w3c.dom.Node;
|
|
import org.w3c.dom.NodeList;
|
|
import org.w3c.dom.Text;
|
|
import org.w3c.dom.UserDataHandler;
|
|
import org.w3c.dom.events.DocumentEvent;
|
|
import org.w3c.dom.events.Event;
|
|
import org.w3c.dom.events.EventException;
|
|
import org.w3c.dom.events.EventListener;
|
|
import org.w3c.dom.events.EventTarget;
|
|
import org.w3c.dom.events.MutationEvent;
|
|
import org.w3c.dom.traversal.NodeFilter;
|
|
import org.w3c.dom.traversal.NodeIterator;
|
|
|
|
/**
|
|
* <p> "Node", "EventTarget", and "DocumentEvent" implementation.
|
|
* This provides most of the core DOM functionality; only more
|
|
* specialized features are provided by subclasses. Those subclasses may
|
|
* have some particular constraints they must implement, by overriding
|
|
* methods defined here. Such constraints are noted here in the method
|
|
* documentation. </p>
|
|
*
|
|
* <p> Note that you can create events with type names prefixed with "USER-",
|
|
* and pass them through this DOM. This lets you use the DOM event scheme
|
|
* for application specific purposes, although you must use a predefined event
|
|
* structure (such as MutationEvent) to pass data along with those events.
|
|
* Test for existence of this feature with the "USER-Events" DOM feature
|
|
* name.</p>
|
|
*
|
|
* <p> Other kinds of events you can send include the "html" events,
|
|
* like "load", "unload", "abort", "error", and "blur"; and the mutation
|
|
* events. If this DOM has been compiled with mutation event support
|
|
* enabled, it will send mutation events when you change parts of the
|
|
* tree; otherwise you may create and send such events yourself, but
|
|
* they won't be generated by the DOM itself. </p>
|
|
*
|
|
* <p> Note that there is a namespace-aware name comparison method,
|
|
* <em>nameAndTypeEquals</em>, which compares the names (and types) of
|
|
* two nodes in conformance with the "Namespaces in XML" specification.
|
|
* While mostly intended for use with elements and attributes, this should
|
|
* also be helpful for ProcessingInstruction nodes and some others which
|
|
* do not have namespace URIs.
|
|
*
|
|
* @author David Brownell
|
|
* @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
|
|
*/
|
|
public abstract class DomNode
|
|
implements Node, NodeList, EventTarget, DocumentEvent, Cloneable, Comparable
|
|
{
|
|
|
|
// package private
|
|
//final static String xmlNamespace = "http://www.w3.org/XML/1998/namespace";
|
|
//final static String xmlnsURI = "http://www.w3.org/2000/xmlns/";
|
|
|
|
// tunable
|
|
// NKIDS_* affects arrays of children (which grow)
|
|
// (currently) fixed size:
|
|
// ANCESTORS_* is for event capture/bubbling, # ancestors
|
|
// NOTIFICATIONS_* is for per-node event delivery, # events
|
|
private static final int NKIDS_DELTA = 8;
|
|
private static final int ANCESTORS_INIT = 20;
|
|
private static final int NOTIFICATIONS_INIT = 10;
|
|
|
|
// tunable: enable mutation events or not? Enabling it costs about
|
|
// 10-15% in DOM construction time, last time it was measured.
|
|
|
|
// package private !!!
|
|
static final boolean reportMutations = true;
|
|
|
|
// locking protocol changeable only within this class
|
|
private static final Object lockNode = new Object();
|
|
|
|
// NON-FINAL class data
|
|
|
|
// Optimize event dispatch by not allocating memory each time
|
|
private static boolean dispatchDataLock;
|
|
private static DomNode[] ancestors = new DomNode[ANCESTORS_INIT];
|
|
private static ListenerRecord[] notificationSet
|
|
= new ListenerRecord[NOTIFICATIONS_INIT];
|
|
|
|
// Ditto for the (most common) event object itself!
|
|
private static boolean eventDataLock;
|
|
private static DomEvent.DomMutationEvent mutationEvent
|
|
= new DomEvent.DomMutationEvent(null);
|
|
|
|
//
|
|
// PER-INSTANCE DATA
|
|
//
|
|
|
|
DomDocument owner;
|
|
DomNode parent; // parent node;
|
|
DomNode previous; // previous sibling node
|
|
DomNode next; // next sibling node
|
|
DomNode first; // first child node
|
|
DomNode last; // last child node
|
|
int index; // index of this node in its parent's children
|
|
int depth; // depth of the node in the document
|
|
int length; // number of children
|
|
final short nodeType;
|
|
|
|
// Bleech ... "package private" so a builder can populate entity refs.
|
|
// writable during construction. DOM spec is nasty.
|
|
boolean readonly;
|
|
|
|
// event registrations
|
|
private HashSet listeners;
|
|
private int nListeners;
|
|
|
|
// DOM Level 3 userData dictionary.
|
|
private HashMap userData;
|
|
private HashMap userDataHandlers;
|
|
|
|
//
|
|
// Some of the methods here are declared 'final' because
|
|
// knowledge about their implementation is built into this
|
|
// class -- for both integrity and performance.
|
|
//
|
|
|
|
/**
|
|
* Reduces space utilization for this node.
|
|
*/
|
|
public void compact()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Constructs a node and associates it with its owner. Only
|
|
* Document and DocumentType nodes may be created with no owner,
|
|
* and DocumentType nodes get an owner as soon as they are
|
|
* associated with a document.
|
|
*/
|
|
protected DomNode(short nodeType, DomDocument owner)
|
|
{
|
|
this.nodeType = nodeType;
|
|
|
|
if (owner == null)
|
|
{
|
|
// DOM calls never go down this path
|
|
if (nodeType != DOCUMENT_NODE && nodeType != DOCUMENT_TYPE_NODE)
|
|
{
|
|
throw new IllegalArgumentException ("no owner!");
|
|
}
|
|
}
|
|
this.owner = owner;
|
|
this.listeners = new HashSet();
|
|
}
|
|
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Returns null; Element subclasses must override this method.
|
|
*/
|
|
public NamedNodeMap getAttributes()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L2></b>
|
|
* Returns true iff this is an element node with attributes.
|
|
*/
|
|
public boolean hasAttributes()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Returns a list, possibly empty, of the children of this node.
|
|
* In this implementation, to conserve memory, nodes are the same
|
|
* as their list of children. This can have ramifications for
|
|
* subclasses, which may need to provide their own getLength method
|
|
* for reasons unrelated to the NodeList method of the same name.
|
|
*/
|
|
public NodeList getChildNodes()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Returns the first child of this node, or null if there are none.
|
|
*/
|
|
public Node getFirstChild()
|
|
{
|
|
return first;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Returns the last child of this node, or null if there are none.
|
|
*/
|
|
public Node getLastChild()
|
|
{
|
|
return last;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Returns true if this node has children.
|
|
*/
|
|
public boolean hasChildNodes()
|
|
{
|
|
return length != 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Exposes the internal "readonly" flag. In DOM, children of
|
|
* entities and entity references are readonly, as are the
|
|
* objects associated with DocumentType objets.
|
|
*/
|
|
public final boolean isReadonly()
|
|
{
|
|
return readonly;
|
|
}
|
|
|
|
/**
|
|
* Sets the internal "readonly" flag so this subtree can't be changed.
|
|
* Subclasses need to override this method for any associated content
|
|
* that's not a child node, such as an element's attributes or the
|
|
* (few) declarations associated with a DocumentType.
|
|
*/
|
|
public void makeReadonly()
|
|
{
|
|
readonly = true;
|
|
for (DomNode child = first; child != null; child = child.next)
|
|
{
|
|
child.makeReadonly();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used to adopt a node to a new document.
|
|
*/
|
|
void setOwner(DomDocument doc)
|
|
{
|
|
this.owner = doc;
|
|
for (DomNode ctx = first; ctx != null; ctx = ctx.next)
|
|
{
|
|
ctx.setOwner(doc);
|
|
}
|
|
}
|
|
|
|
// just checks the node for inclusion -- may be called many
|
|
// times (docfrag) before anything is allowed to change
|
|
private void checkMisc(DomNode child)
|
|
{
|
|
if (readonly && !owner.building)
|
|
{
|
|
throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
|
|
null, this, 0);
|
|
}
|
|
for (DomNode ctx = this; ctx != null; ctx = ctx.parent)
|
|
{
|
|
if (child == ctx)
|
|
{
|
|
throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
|
|
"can't make ancestor into a child",
|
|
this, 0);
|
|
}
|
|
}
|
|
|
|
DomDocument owner = (nodeType == DOCUMENT_NODE) ? (DomDocument) this :
|
|
this.owner;
|
|
DomDocument childOwner = child.owner;
|
|
short childNodeType = child.nodeType;
|
|
|
|
if (childOwner != owner)
|
|
{
|
|
// new in DOM L2, this case -- patch it up later, in reparent()
|
|
if (!(childNodeType == DOCUMENT_TYPE_NODE && childOwner == null))
|
|
{
|
|
throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
|
|
null, child, 0);
|
|
}
|
|
}
|
|
|
|
// enforce various structural constraints
|
|
switch (nodeType)
|
|
{
|
|
case DOCUMENT_NODE:
|
|
switch (childNodeType)
|
|
{
|
|
case ELEMENT_NODE:
|
|
case PROCESSING_INSTRUCTION_NODE:
|
|
case COMMENT_NODE:
|
|
case DOCUMENT_TYPE_NODE:
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case ATTRIBUTE_NODE:
|
|
switch (childNodeType)
|
|
{
|
|
case TEXT_NODE:
|
|
case ENTITY_REFERENCE_NODE:
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case DOCUMENT_FRAGMENT_NODE:
|
|
case ENTITY_REFERENCE_NODE:
|
|
case ELEMENT_NODE:
|
|
case ENTITY_NODE:
|
|
switch (childNodeType)
|
|
{
|
|
case ELEMENT_NODE:
|
|
case TEXT_NODE:
|
|
case COMMENT_NODE:
|
|
case PROCESSING_INSTRUCTION_NODE:
|
|
case CDATA_SECTION_NODE:
|
|
case ENTITY_REFERENCE_NODE:
|
|
return;
|
|
}
|
|
break;
|
|
case DOCUMENT_TYPE_NODE:
|
|
if (!owner.building)
|
|
break;
|
|
switch (childNodeType)
|
|
{
|
|
case COMMENT_NODE:
|
|
case PROCESSING_INSTRUCTION_NODE:
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
if (owner.checkingWellformedness)
|
|
{
|
|
throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
|
|
"can't append " +
|
|
nodeTypeToString(childNodeType) +
|
|
" to node of type " +
|
|
nodeTypeToString(nodeType),
|
|
this, 0);
|
|
}
|
|
}
|
|
|
|
// Here's hoping a good optimizer will detect the case when the
|
|
// next several methods are never called, and won't allocate
|
|
// object code space of any kind. (Case: not reporting any
|
|
// mutation events. We can also remove some static variables
|
|
// listed above.)
|
|
|
|
private void insertionEvent(DomEvent.DomMutationEvent event,
|
|
DomNode target)
|
|
{
|
|
if (owner == null || owner.building)
|
|
{
|
|
return;
|
|
}
|
|
boolean doFree = false;
|
|
|
|
if (event == null)
|
|
{
|
|
event = getMutationEvent();
|
|
}
|
|
if (event != null)
|
|
{
|
|
doFree = true;
|
|
}
|
|
else
|
|
{
|
|
event = new DomEvent.DomMutationEvent(null);
|
|
}
|
|
event.initMutationEvent("DOMNodeInserted",
|
|
true /* bubbles */, false /* nocancel */,
|
|
this /* related */, null, null, null, (short) 0);
|
|
target.dispatchEvent(event);
|
|
|
|
// XXX should really visit every descendant of 'target'
|
|
// and sent a DOMNodeInsertedIntoDocument event to it...
|
|
// bleech, there's no way to keep that acceptably fast.
|
|
|
|
if (doFree)
|
|
{
|
|
event.target = null;
|
|
event.relatedNode = null;
|
|
event.currentNode = null;
|
|
eventDataLock = false;
|
|
} // else we created work for the GC
|
|
}
|
|
|
|
private void removalEvent(DomEvent.DomMutationEvent event,
|
|
DomNode target)
|
|
{
|
|
if (owner == null || owner.building)
|
|
{
|
|
return;
|
|
}
|
|
boolean doFree = false;
|
|
|
|
if (event == null)
|
|
{
|
|
event = getMutationEvent();
|
|
}
|
|
if (event != null)
|
|
{
|
|
doFree = true;
|
|
}
|
|
else
|
|
{
|
|
event = new DomEvent.DomMutationEvent(null);
|
|
}
|
|
event.initMutationEvent("DOMNodeRemoved",
|
|
true /* bubbles */, false /* nocancel */,
|
|
this /* related */, null, null, null, (short) 0);
|
|
target.dispatchEvent(event);
|
|
|
|
// XXX should really visit every descendant of 'target'
|
|
// and sent a DOMNodeRemovedFromDocument event to it...
|
|
// bleech, there's no way to keep that acceptably fast.
|
|
|
|
event.target = null;
|
|
event.relatedNode = null;
|
|
event.currentNode = null;
|
|
if (doFree)
|
|
{
|
|
eventDataLock = false;
|
|
}
|
|
// else we created more work for the GC
|
|
}
|
|
|
|
//
|
|
// Avoid creating lots of memory management work, by using a simple
|
|
// allocation strategy for the mutation event objects that get used
|
|
// at least once per tree modification. We can't use stack allocation,
|
|
// so we do the next simplest thing -- more or less, static allocation.
|
|
// Concurrent notifications should be rare, anyway.
|
|
//
|
|
// Returns the preallocated object, which needs to be carefully freed,
|
|
// or null to indicate the caller needs to allocate their own.
|
|
//
|
|
static private DomEvent.DomMutationEvent getMutationEvent()
|
|
{
|
|
synchronized (lockNode)
|
|
{
|
|
if (eventDataLock)
|
|
{
|
|
return null;
|
|
}
|
|
eventDataLock = true;
|
|
return mutationEvent;
|
|
}
|
|
}
|
|
|
|
// NOTE: this is manually inlined in the insertion
|
|
// and removal event methods above; change in sync.
|
|
static private void freeMutationEvent()
|
|
{
|
|
// clear fields to enable GC
|
|
mutationEvent.clear();
|
|
eventDataLock = false;
|
|
}
|
|
|
|
void setDepth(int depth)
|
|
{
|
|
this.depth = depth;
|
|
for (DomNode ctx = first; ctx != null; ctx = ctx.next)
|
|
{
|
|
ctx.setDepth(depth + 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Appends the specified node to this node's list of children.
|
|
* Document subclasses must override this to enforce the restrictions
|
|
* that there be only one element and document type child.
|
|
*
|
|
* <p> Causes a DOMNodeInserted mutation event to be reported.
|
|
* Will first cause a DOMNodeRemoved event to be reported if the
|
|
* parameter already has a parent. If the new child is a document
|
|
* fragment node, both events will be reported for each child of
|
|
* the fragment; the order in which children are removed and
|
|
* inserted is implementation-specific.
|
|
*
|
|
* <p> If this DOM has been compiled without mutation event support,
|
|
* these events will not be reported.
|
|
*/
|
|
public Node appendChild(Node newChild)
|
|
{
|
|
try
|
|
{
|
|
DomNode child = (DomNode) newChild;
|
|
|
|
if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
|
|
{
|
|
// Append all nodes in the fragment to this node
|
|
for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
|
|
{
|
|
checkMisc(ctx);
|
|
}
|
|
for (DomNode ctx = child.first; ctx != null; )
|
|
{
|
|
DomNode ctxNext = ctx.next;
|
|
appendChild(ctx);
|
|
ctx = ctxNext;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
checkMisc(child);
|
|
if (child.parent != null)
|
|
{
|
|
child.parent.removeChild(child);
|
|
}
|
|
child.parent = this;
|
|
child.index = length++;
|
|
child.setDepth(depth + 1);
|
|
child.next = null;
|
|
if (last == null)
|
|
{
|
|
first = child;
|
|
child.previous = null;
|
|
}
|
|
else
|
|
{
|
|
last.next = child;
|
|
child.previous = last;
|
|
}
|
|
last = child;
|
|
|
|
if (reportMutations)
|
|
{
|
|
insertionEvent(null, child);
|
|
}
|
|
}
|
|
|
|
return child;
|
|
}
|
|
catch (ClassCastException e)
|
|
{
|
|
throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
|
|
null, newChild, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Inserts the specified node in this node's list of children.
|
|
* Document subclasses must override this to enforce the restrictions
|
|
* that there be only one element and document type child.
|
|
*
|
|
* <p> Causes a DOMNodeInserted mutation event to be reported. Will
|
|
* first cause a DOMNodeRemoved event to be reported if the newChild
|
|
* parameter already has a parent. If the new child is a document
|
|
* fragment node, both events will be reported for each child of
|
|
* the fragment; the order in which children are removed and inserted
|
|
* is implementation-specific.
|
|
*
|
|
* <p> If this DOM has been compiled without mutation event support,
|
|
* these events will not be reported.
|
|
*/
|
|
public Node insertBefore(Node newChild, Node refChild)
|
|
{
|
|
if (refChild == null)
|
|
{
|
|
return appendChild(newChild);
|
|
}
|
|
|
|
try
|
|
{
|
|
DomNode child = (DomNode) newChild;
|
|
DomNode ref = (DomNode) refChild;
|
|
|
|
if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
|
|
{
|
|
// Append all nodes in the fragment to this node
|
|
for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
|
|
{
|
|
checkMisc(ctx);
|
|
}
|
|
for (DomNode ctx = child.first; ctx != null; )
|
|
{
|
|
DomNode ctxNext = ctx.next;
|
|
insertBefore(ctx, ref);
|
|
ctx = ctxNext;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
checkMisc(child);
|
|
if (ref == null || ref.parent != this)
|
|
{
|
|
throw new DomDOMException(DOMException.NOT_FOUND_ERR,
|
|
null, ref, 0);
|
|
}
|
|
if (ref == child)
|
|
{
|
|
throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
|
|
"can't insert node before itself",
|
|
ref, 0);
|
|
}
|
|
|
|
if (child.parent != null)
|
|
{
|
|
child.parent.removeChild(child);
|
|
}
|
|
child.parent = this;
|
|
int i = ref.index;
|
|
child.setDepth(depth + 1);
|
|
child.next = ref;
|
|
if (ref.previous != null)
|
|
{
|
|
ref.previous.next = child;
|
|
}
|
|
child.previous = ref.previous;
|
|
ref.previous = child;
|
|
if (first == ref)
|
|
{
|
|
first = child;
|
|
}
|
|
// index renumbering
|
|
for (DomNode ctx = child; ctx != null; ctx = ctx.next)
|
|
{
|
|
ctx.index = i++;
|
|
}
|
|
|
|
if (reportMutations)
|
|
{
|
|
insertionEvent(null, child);
|
|
}
|
|
length++;
|
|
}
|
|
|
|
return child;
|
|
}
|
|
catch (ClassCastException e)
|
|
{
|
|
throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
|
|
null, newChild, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Replaces the specified node in this node's list of children.
|
|
* Document subclasses must override this to test the restrictions
|
|
* that there be only one element and document type child.
|
|
*
|
|
* <p> Causes DOMNodeRemoved and DOMNodeInserted mutation event to be
|
|
* reported. Will cause another DOMNodeRemoved event to be reported if
|
|
* the newChild parameter already has a parent. These events may be
|
|
* delivered in any order, except that the event reporting removal
|
|
* from such an existing parent will always be delivered before the
|
|
* event reporting its re-insertion as a child of some other node.
|
|
* The order in which children are removed and inserted is implementation
|
|
* specific.
|
|
*
|
|
* <p> If your application needs to depend on the in which those removal
|
|
* and insertion events are delivered, don't use this API. Instead,
|
|
* invoke the removeChild and insertBefore methods directly, to guarantee
|
|
* a specific delivery order. Similarly, don't use document fragments,
|
|
* Otherwise your application code may not work on a DOM which implements
|
|
* this method differently.
|
|
*
|
|
* <p> If this DOM has been compiled without mutation event support,
|
|
* these events will not be reported.
|
|
*/
|
|
public Node replaceChild(Node newChild, Node refChild)
|
|
{
|
|
try
|
|
{
|
|
DomNode child = (DomNode) newChild;
|
|
DomNode ref = (DomNode) refChild;
|
|
|
|
DomEvent.DomMutationEvent event = getMutationEvent();
|
|
boolean doFree = (event != null);
|
|
|
|
if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
|
|
{
|
|
// Append all nodes in the fragment to this node
|
|
for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
|
|
{
|
|
checkMisc(ctx);
|
|
}
|
|
if (ref == null || ref.parent != this)
|
|
{
|
|
throw new DomDOMException(DOMException.NOT_FOUND_ERR,
|
|
null, ref, 0);
|
|
}
|
|
|
|
if (reportMutations)
|
|
{
|
|
removalEvent(event, ref);
|
|
}
|
|
length--;
|
|
length += child.length;
|
|
|
|
if (child.length == 0)
|
|
{
|
|
// Removal
|
|
if (ref.previous != null)
|
|
{
|
|
ref.previous.next = ref.next;
|
|
}
|
|
if (ref.next != null)
|
|
{
|
|
ref.next.previous = ref.previous;
|
|
}
|
|
if (first == ref)
|
|
{
|
|
first = ref.next;
|
|
}
|
|
if (last == ref)
|
|
{
|
|
last = ref.previous;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i = ref.index;
|
|
for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
|
|
{
|
|
// Insertion
|
|
ctx.parent = this;
|
|
ctx.index = i++;
|
|
ctx.setDepth(ref.depth);
|
|
if (ctx == child.first)
|
|
{
|
|
ctx.previous = ref.previous;
|
|
}
|
|
if (ctx == child.last)
|
|
{
|
|
ctx.next = ref.next;
|
|
}
|
|
}
|
|
if (first == ref)
|
|
{
|
|
first = child.first;
|
|
}
|
|
if (last == ref)
|
|
{
|
|
last = child.last;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
checkMisc(child);
|
|
if (ref == null || ref.parent != this)
|
|
{
|
|
throw new DomDOMException(DOMException.NOT_FOUND_ERR,
|
|
null, ref, 0);
|
|
}
|
|
|
|
if (reportMutations)
|
|
{
|
|
removalEvent(event, ref);
|
|
}
|
|
|
|
if (child.parent != null)
|
|
{
|
|
child.parent.removeChild(child);
|
|
}
|
|
child.parent = this;
|
|
child.index = ref.index;
|
|
child.setDepth(ref.depth);
|
|
if (ref.previous != null)
|
|
{
|
|
ref.previous.next = child;
|
|
}
|
|
child.previous = ref.previous;
|
|
if (ref.next != null)
|
|
{
|
|
ref.next.previous = child;
|
|
}
|
|
child.next = ref.next;
|
|
if (first == ref)
|
|
{
|
|
first = child;
|
|
}
|
|
if (last == ref)
|
|
{
|
|
last = child;
|
|
}
|
|
|
|
if (reportMutations)
|
|
{
|
|
insertionEvent(event, child);
|
|
}
|
|
if (doFree)
|
|
{
|
|
freeMutationEvent();
|
|
}
|
|
}
|
|
ref.parent = null;
|
|
ref.index = 0;
|
|
ref.setDepth(0);
|
|
ref.previous = null;
|
|
ref.next = null;
|
|
|
|
return ref;
|
|
}
|
|
catch (ClassCastException e)
|
|
{
|
|
throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
|
|
null, newChild, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Removes the specified child from this node's list of children,
|
|
* or else reports an exception.
|
|
*
|
|
* <p> Causes a DOMNodeRemoved mutation event to be reported.
|
|
*
|
|
* <p> If this DOM has been compiled without mutation event support,
|
|
* these events will not be reported.
|
|
*/
|
|
public Node removeChild(Node refChild)
|
|
{
|
|
try
|
|
{
|
|
DomNode ref = (DomNode) refChild;
|
|
|
|
if (ref == null || ref.parent != this)
|
|
{
|
|
throw new DomDOMException(DOMException.NOT_FOUND_ERR,
|
|
null, ref, 0);
|
|
}
|
|
if (readonly && !owner.building)
|
|
{
|
|
throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
|
|
null, this, 0);
|
|
}
|
|
|
|
for (DomNode child = first; child != null; child = child.next)
|
|
{
|
|
if (child == ref)
|
|
{
|
|
if (reportMutations)
|
|
{
|
|
removalEvent(null, child);
|
|
}
|
|
|
|
length--;
|
|
if (ref.previous != null)
|
|
{
|
|
ref.previous.next = ref.next;
|
|
}
|
|
if (ref.next != null)
|
|
{
|
|
ref.next.previous = ref.previous;
|
|
}
|
|
if (first == ref)
|
|
{
|
|
first = ref.next;
|
|
}
|
|
if (last == ref)
|
|
{
|
|
last = ref.previous;
|
|
}
|
|
// renumber indices
|
|
int i = 0;
|
|
for (DomNode ctx = first; ctx != null; ctx = ctx.next)
|
|
{
|
|
ctx.index = i++;
|
|
}
|
|
ref.parent = null;
|
|
ref.setDepth(0);
|
|
ref.index = 0;
|
|
ref.previous = null;
|
|
ref.next = null;
|
|
|
|
return ref;
|
|
}
|
|
}
|
|
throw new DomDOMException(DOMException.NOT_FOUND_ERR,
|
|
"that's no child of mine", refChild, 0);
|
|
}
|
|
catch (ClassCastException e)
|
|
{
|
|
throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
|
|
null, refChild, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1 (NodeList)</b>
|
|
* Returns the item with the specified index in this NodeList,
|
|
* else null.
|
|
*/
|
|
public Node item(int index)
|
|
{
|
|
DomNode child = first;
|
|
int count = 0;
|
|
while (child != null && count < index)
|
|
{
|
|
child = child.next;
|
|
count++;
|
|
}
|
|
return child;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1 (NodeList)</b>
|
|
* Returns the number of elements in this NodeList.
|
|
* (Note that many interfaces have a "Length" property, not just
|
|
* NodeList, and if a node subtype must implement one of those,
|
|
* it will also need to override getChildNodes.)
|
|
*/
|
|
public int getLength()
|
|
{
|
|
return length;
|
|
}
|
|
|
|
/**
|
|
* Minimize extra space consumed by this node to hold children and event
|
|
* listeners.
|
|
*/
|
|
public void trimToSize()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Returns the previous sibling, if one is known.
|
|
*/
|
|
public Node getNextSibling()
|
|
{
|
|
return next;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Returns the previous sibling, if one is known.
|
|
*/
|
|
public Node getPreviousSibling()
|
|
{
|
|
return previous;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Returns the parent node, if one is known.
|
|
*/
|
|
public Node getParentNode()
|
|
{
|
|
return parent;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L2</b>
|
|
* Consults the DOM implementation to determine if the requested
|
|
* feature is supported. DocumentType subclasses must override
|
|
* this method, and associate themselves directly with the
|
|
* DOMImplementation node used. (This method relies on being able
|
|
* to access the DOMImplementation from the owner document, but
|
|
* DocumentType nodes can be created without an owner.)
|
|
*/
|
|
public boolean isSupported(String feature, String version)
|
|
{
|
|
Document doc = owner;
|
|
DOMImplementation impl = null;
|
|
|
|
if (doc == null && nodeType == DOCUMENT_NODE)
|
|
{
|
|
doc = (Document) this;
|
|
}
|
|
|
|
if (doc == null)
|
|
{
|
|
// possible for DocumentType
|
|
throw new IllegalStateException ("unbound ownerDocument");
|
|
}
|
|
|
|
impl = doc.getImplementation();
|
|
return impl.hasFeature(feature, version);
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1 (modified in L2)</b>
|
|
* Returns the owner document. This is only null for Document nodes,
|
|
* and (new in L2) for DocumentType nodes which have not yet been
|
|
* associated with the rest of their document.
|
|
*/
|
|
final public Document getOwnerDocument()
|
|
{
|
|
return owner;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Does nothing; this must be overridden (along with the
|
|
* getNodeValue method) for nodes with a non-null defined value.
|
|
*/
|
|
public void setNodeValue(String value)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Returns null; this must be overridden for nodes types with
|
|
* a defined value, along with the setNodeValue method.
|
|
*/
|
|
public String getNodeValue()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/** This forces GCJ compatibility.
|
|
* Without this method GCJ is unable to compile to byte code.
|
|
*/
|
|
public final short getNodeType()
|
|
{
|
|
return nodeType;
|
|
}
|
|
|
|
/** This forces GCJ compatibility.
|
|
* Without this method GCJ seems unable to natively compile GNUJAXP.
|
|
*/
|
|
public abstract String getNodeName();
|
|
|
|
/**
|
|
* <b>DOM L2</b>
|
|
* Does nothing; this must be overridden (along with the
|
|
* getPrefix method) for element and attribute nodes.
|
|
*/
|
|
public void setPrefix(String prefix)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L2</b>
|
|
* Returns null; this must be overridden for element and
|
|
* attribute nodes.
|
|
*/
|
|
public String getPrefix()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L2</b>
|
|
* Returns null; this must be overridden for element and
|
|
* attribute nodes.
|
|
*/
|
|
public String getNamespaceURI()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L2</b>
|
|
* Returns the node name; this must be overridden for element and
|
|
* attribute nodes.
|
|
*/
|
|
public String getLocalName()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Returns a clone of this node which optionally includes cloned
|
|
* versions of child nodes. Clones are always mutable, except for
|
|
* entity reference nodes.
|
|
*/
|
|
public Node cloneNode(boolean deep)
|
|
{
|
|
if (deep)
|
|
{
|
|
return cloneNodeDeepInternal(true, null);
|
|
}
|
|
|
|
DomNode node = (DomNode) clone();
|
|
if (nodeType == ENTITY_REFERENCE_NODE)
|
|
{
|
|
node.makeReadonly();
|
|
}
|
|
notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node);
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Returns a deep clone of this node.
|
|
*/
|
|
private DomNode cloneNodeDeepInternal(boolean root, DomDocument doc)
|
|
{
|
|
DomNode node = (DomNode) clone();
|
|
boolean building = false; // Never used unless root is true
|
|
if (root)
|
|
{
|
|
doc = (nodeType == DOCUMENT_NODE) ? (DomDocument) node : node.owner;
|
|
building = doc.building;
|
|
doc.building = true; // Permit certain structural rules
|
|
}
|
|
node.owner = doc;
|
|
for (DomNode ctx = first; ctx != null; ctx = ctx.next)
|
|
{
|
|
DomNode newChild = ctx.cloneNodeDeepInternal(false, doc);
|
|
node.appendChild(newChild);
|
|
}
|
|
if (nodeType == ENTITY_REFERENCE_NODE)
|
|
{
|
|
node.makeReadonly();
|
|
}
|
|
if (root)
|
|
{
|
|
doc.building = building;
|
|
}
|
|
notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node);
|
|
return node;
|
|
}
|
|
|
|
void notifyUserDataHandlers(short op, Node src, Node dst)
|
|
{
|
|
if (userDataHandlers != null)
|
|
{
|
|
for (Iterator i = userDataHandlers.entrySet().iterator(); i.hasNext(); )
|
|
{
|
|
Map.Entry entry = (Map.Entry) i.next();
|
|
String key = (String) entry.getKey();
|
|
UserDataHandler handler = (UserDataHandler) entry.getValue();
|
|
Object data = userData.get(key);
|
|
handler.handle(op, key, data, src, dst);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clones this node; roughly equivalent to cloneNode(false).
|
|
* Element subclasses must provide a new implementation which
|
|
* invokes this method to handle the basics, and then arranges
|
|
* to clone any element attributes directly. Attribute subclasses
|
|
* must make similar arrangements, ensuring that existing ties to
|
|
* elements are broken by cloning.
|
|
*/
|
|
public Object clone()
|
|
{
|
|
try
|
|
{
|
|
DomNode node = (DomNode) super.clone();
|
|
|
|
node.parent = null;
|
|
node.depth = 0;
|
|
node.index = 0;
|
|
node.length = 0;
|
|
node.first = null;
|
|
node.last = null;
|
|
node.previous = null;
|
|
node.next = null;
|
|
|
|
node.readonly = false;
|
|
node.listeners = new HashSet();
|
|
node.nListeners = 0;
|
|
return node;
|
|
|
|
}
|
|
catch (CloneNotSupportedException x)
|
|
{
|
|
throw new Error("clone didn't work");
|
|
}
|
|
}
|
|
|
|
// the elements-by-tagname stuff is needed for both
|
|
// elements and documents ... this is in lieu of a
|
|
// common base class between Node and NodeNS.
|
|
|
|
/**
|
|
* <b>DOM L1</b>
|
|
* Creates a NodeList giving array-style access to elements with
|
|
* the specified name. Access is fastest if indices change by
|
|
* small values, and the DOM is not modified.
|
|
*/
|
|
public NodeList getElementsByTagName(String tag)
|
|
{
|
|
return new ShadowList(null, tag);
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L2</b>
|
|
* Creates a NodeList giving array-style access to elements with
|
|
* the specified namespace and local name. Access is fastest if
|
|
* indices change by small values, and the DOM is not modified.
|
|
*/
|
|
public NodeList getElementsByTagNameNS(String namespace, String local)
|
|
{
|
|
return new ShadowList(namespace, local);
|
|
}
|
|
|
|
|
|
//
|
|
// This shadow class is GC-able even when the live list it shadows
|
|
// can't be, because of event registration hookups. Its finalizer
|
|
// makes that live list become GC-able.
|
|
//
|
|
final class ShadowList
|
|
implements NodeList
|
|
{
|
|
|
|
private LiveNodeList liveList;
|
|
|
|
ShadowList(String ns, String local)
|
|
{
|
|
liveList = new LiveNodeList(ns, local);
|
|
}
|
|
|
|
public void finalize()
|
|
{
|
|
liveList.detach();
|
|
liveList = null;
|
|
}
|
|
|
|
public Node item(int index)
|
|
{
|
|
return liveList.item(index);
|
|
}
|
|
|
|
public int getLength()
|
|
{
|
|
return liveList.getLength();
|
|
}
|
|
}
|
|
|
|
final class LiveNodeList
|
|
implements NodeList, EventListener, NodeFilter
|
|
{
|
|
|
|
private final boolean matchAnyURI;
|
|
private final boolean matchAnyName;
|
|
private final String elementURI;
|
|
private final String elementName;
|
|
|
|
private DomIterator current;
|
|
private int lastIndex;
|
|
|
|
LiveNodeList(String uri, String name)
|
|
{
|
|
elementURI = uri;
|
|
elementName = name;
|
|
matchAnyURI = "*".equals(uri);
|
|
matchAnyName = "*".equals(name);
|
|
|
|
DomNode.this.addEventListener("DOMNodeInserted", this, true);
|
|
DomNode.this.addEventListener("DOMNodeRemoved", this, true);
|
|
}
|
|
|
|
void detach()
|
|
{
|
|
if (current != null)
|
|
current.detach();
|
|
current = null;
|
|
|
|
DomNode.this.removeEventListener("DOMNodeInserted", this, true);
|
|
DomNode.this.removeEventListener("DOMNodeRemoved", this, true);
|
|
}
|
|
|
|
public short acceptNode(Node element)
|
|
{
|
|
if (element == DomNode.this)
|
|
{
|
|
return FILTER_SKIP;
|
|
}
|
|
|
|
// use namespace-aware matching ...
|
|
if (elementURI != null)
|
|
{
|
|
if (!(matchAnyURI
|
|
|| elementURI.equals(element.getNamespaceURI())))
|
|
{
|
|
return FILTER_SKIP;
|
|
}
|
|
if (!(matchAnyName
|
|
|| elementName.equals(element.getLocalName())))
|
|
{
|
|
return FILTER_SKIP;
|
|
}
|
|
|
|
// ... or qName-based kind.
|
|
}
|
|
else
|
|
{
|
|
if (!(matchAnyName
|
|
|| elementName.equals(element.getNodeName())))
|
|
{
|
|
return FILTER_SKIP;
|
|
}
|
|
}
|
|
return FILTER_ACCEPT;
|
|
}
|
|
|
|
private DomIterator createIterator()
|
|
{
|
|
return new DomIterator(DomNode.this,
|
|
NodeFilter.SHOW_ELEMENT,
|
|
this, /* filter */
|
|
true /* expand entity refs */
|
|
);
|
|
}
|
|
|
|
public void handleEvent(Event e)
|
|
{
|
|
MutationEvent mutation = (MutationEvent) e;
|
|
Node related = mutation.getRelatedNode();
|
|
|
|
// XXX if it's got children ... check all kids too, they
|
|
// will invalidate our saved index
|
|
|
|
if (related.getNodeType() != Node.ELEMENT_NODE ||
|
|
related.getNodeName() != elementName ||
|
|
related.getNamespaceURI() != elementURI)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (current != null)
|
|
current.detach();
|
|
current = null;
|
|
}
|
|
|
|
public Node item(int index)
|
|
{
|
|
if (current == null)
|
|
{
|
|
current = createIterator();
|
|
lastIndex = -1;
|
|
}
|
|
|
|
// last node or before? go backwards
|
|
if (index <= lastIndex) {
|
|
while (index != lastIndex) {
|
|
current.previousNode ();
|
|
lastIndex--;
|
|
}
|
|
Node ret = current.previousNode ();
|
|
current.detach();
|
|
current = null;
|
|
return ret;
|
|
}
|
|
|
|
// somewhere after last node
|
|
while (++lastIndex != index)
|
|
current.nextNode ();
|
|
|
|
Node ret = current.nextNode ();
|
|
current.detach();
|
|
current = null;
|
|
return ret;
|
|
}
|
|
|
|
public int getLength()
|
|
{
|
|
int retval = 0;
|
|
NodeIterator iter = createIterator();
|
|
|
|
while (iter.nextNode() != null)
|
|
{
|
|
retval++;
|
|
}
|
|
iter.detach();
|
|
return retval;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// EventTarget support
|
|
//
|
|
static final class ListenerRecord
|
|
{
|
|
|
|
String type;
|
|
EventListener listener;
|
|
boolean useCapture;
|
|
|
|
// XXX use JDK 1.2 java.lang.ref.WeakReference to listener,
|
|
// and we can both get rid of "shadow" classes and remove
|
|
// the need for applications to apply similar trix ... but
|
|
// JDK 1.2 support isn't generally available yet
|
|
|
|
ListenerRecord(String type, EventListener listener, boolean useCapture)
|
|
{
|
|
this.type = type.intern();
|
|
this.listener = listener;
|
|
this.useCapture = useCapture;
|
|
}
|
|
|
|
public boolean equals(Object o)
|
|
{
|
|
ListenerRecord rec = (ListenerRecord)o;
|
|
return listener == rec.listener
|
|
&& useCapture == rec.useCapture
|
|
&& type == rec.type;
|
|
}
|
|
|
|
public int hashCode()
|
|
{
|
|
return listener.hashCode() ^ type.hashCode();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L2 (Events)</b>
|
|
* Returns an instance of the specified type of event object.
|
|
* Understands about DOM Mutation, HTML, and UI events.
|
|
*
|
|
* <p>If the name of the event type begins with "USER-", then an object
|
|
* implementing the "Event" class will be returned; this provides a
|
|
* limited facility for application-defined events to use the DOM event
|
|
* infrastructure. Alternatively, use one of the standard DOM event
|
|
* classes and initialize it using use such a "USER-" event type name;
|
|
* or defin, instantiate, and initialize an application-specific subclass
|
|
* of DomEvent and pass that to dispatchEvent().
|
|
*
|
|
* @param eventType Identifies the particular DOM feature module
|
|
* defining the type of event, such as "MutationEvents".
|
|
* <em>The event "name" is a different kind of "type".</em>
|
|
*/
|
|
public Event createEvent(String eventType)
|
|
{
|
|
eventType = eventType.toLowerCase();
|
|
|
|
if ("mutationevents".equals(eventType))
|
|
{
|
|
return new DomEvent.DomMutationEvent(null);
|
|
}
|
|
|
|
if ("htmlevents".equals(eventType)
|
|
|| "events".equals(eventType)
|
|
|| "user-events".equals(eventType))
|
|
{
|
|
return new DomEvent(null);
|
|
}
|
|
|
|
if ("uievents".equals(eventType))
|
|
{
|
|
return new DomEvent.DomUIEvent(null);
|
|
}
|
|
|
|
// mouse events
|
|
|
|
throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR,
|
|
eventType, null, 0);
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L2 (Events)</b>
|
|
* Registers an event listener's interest in a class of events.
|
|
*/
|
|
public final void addEventListener(String type,
|
|
EventListener listener,
|
|
boolean useCapture)
|
|
{
|
|
// prune duplicates
|
|
ListenerRecord record;
|
|
|
|
record = new ListenerRecord(type, listener, useCapture);
|
|
listeners.add(record);
|
|
nListeners = listeners.size();
|
|
}
|
|
|
|
// XXX this exception should be discarded from DOM
|
|
|
|
// this class can be instantiated, unlike the one in the spec
|
|
static final class DomEventException
|
|
extends EventException
|
|
{
|
|
|
|
DomEventException()
|
|
{
|
|
super(UNSPECIFIED_EVENT_TYPE_ERR, "unspecified event type");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L2 (Events)</b>
|
|
* Delivers an event to all relevant listeners, returning true if the
|
|
* caller should perform their default action. Note that the event
|
|
* must have been provided by the createEvent() method on this
|
|
* class, else it can't be dispatched.
|
|
*
|
|
* @see #createEvent
|
|
*
|
|
* @exception NullPointerException When a null event is passed.
|
|
* @exception ClassCastException When the event wasn't provided by
|
|
* the createEvent method, or otherwise isn't a DomEvent.
|
|
* @exception EventException If the event type wasn't specified
|
|
*/
|
|
public final boolean dispatchEvent(Event event)
|
|
throws EventException
|
|
{
|
|
DomEvent e = (DomEvent) event;
|
|
DomNode[] ancestors = null;
|
|
int ancestorMax = 0;
|
|
boolean haveDispatchDataLock = false;
|
|
|
|
if (e.type == null)
|
|
{
|
|
throw new DomEventException();
|
|
}
|
|
|
|
e.doDefault = true;
|
|
e.target = this;
|
|
|
|
//
|
|
// Typical case: one nonrecursive dispatchEvent call at a time
|
|
// for this class. If that's our case, we can avoid allocating
|
|
// garbage, which is overall a big win. Even with advanced GCs
|
|
// that deal well with short-lived garbage, and wayfast allocators,
|
|
// it still helps.
|
|
//
|
|
// Remember -- EVERY mutation goes though here at least once.
|
|
//
|
|
// When populating a DOM tree, trying to send mutation events is
|
|
// the primary cost; this dominates the critical path.
|
|
//
|
|
try
|
|
{
|
|
DomNode current;
|
|
int index;
|
|
boolean haveAncestorRegistrations = false;
|
|
ListenerRecord[] notificationSet;
|
|
int ancestorLen;
|
|
|
|
synchronized (lockNode)
|
|
{
|
|
if (!dispatchDataLock)
|
|
{
|
|
haveDispatchDataLock = dispatchDataLock = true;
|
|
notificationSet = DomNode.notificationSet;
|
|
ancestors = DomNode.ancestors;
|
|
}
|
|
else
|
|
{
|
|
notificationSet = new ListenerRecord[NOTIFICATIONS_INIT];
|
|
ancestors = new DomNode[ANCESTORS_INIT];
|
|
}
|
|
ancestorLen = ancestors.length;
|
|
}
|
|
|
|
// Climb to the top of this subtree and handle capture, letting
|
|
// each node (from the top down) capture until one stops it or
|
|
// until we get to this one.
|
|
current = (parent == null) ? this : parent;
|
|
if (current.depth >= ANCESTORS_INIT)
|
|
{
|
|
DomNode[] newants = new DomNode[current.depth + 1];
|
|
System.arraycopy(ancestors, 0, newants, 0, ancestors.length);
|
|
ancestors = newants;
|
|
ancestorLen = ancestors.length;
|
|
}
|
|
for (index = 0; index < ancestorLen; index++)
|
|
{
|
|
if (current == null || current.depth == 0)
|
|
break;
|
|
|
|
if (current.nListeners != 0)
|
|
{
|
|
haveAncestorRegistrations = true;
|
|
}
|
|
ancestors [index] = current;
|
|
current = current.parent;
|
|
}
|
|
if (current.depth > 0)
|
|
{
|
|
throw new RuntimeException("dispatchEvent capture stack size");
|
|
}
|
|
|
|
ancestorMax = index;
|
|
e.stop = false;
|
|
|
|
if (haveAncestorRegistrations)
|
|
{
|
|
e.eventPhase = Event.CAPTURING_PHASE;
|
|
while (!e.stop && index-- > 0)
|
|
{
|
|
current = ancestors [index];
|
|
if (current.nListeners != 0)
|
|
{
|
|
notifyNode(e, current, true, notificationSet);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Always deliver events to the target node (this)
|
|
// unless stopPropagation was called. If we saw
|
|
// no registrations yet (typical!), we never will.
|
|
if (!e.stop && nListeners != 0)
|
|
{
|
|
e.eventPhase = Event.AT_TARGET;
|
|
notifyNode (e, this, false, notificationSet);
|
|
}
|
|
else if (!haveAncestorRegistrations)
|
|
{
|
|
e.stop = true;
|
|
}
|
|
|
|
// If the event bubbles and propagation wasn't halted,
|
|
// walk back up the ancestor list. Stop bubbling when
|
|
// any bubbled event handler stops it.
|
|
|
|
if (!e.stop && e.bubbles)
|
|
{
|
|
e.eventPhase = Event.BUBBLING_PHASE;
|
|
for (index = 0;
|
|
!e.stop
|
|
&& index < ancestorMax
|
|
&& (current = ancestors[index]) != null;
|
|
index++)
|
|
{
|
|
if (current.nListeners != 0)
|
|
{
|
|
notifyNode(e, current, false, notificationSet);
|
|
}
|
|
}
|
|
}
|
|
e.eventPhase = 0;
|
|
|
|
// Caller chooses whether to perform the default
|
|
// action based on return from this method.
|
|
return e.doDefault;
|
|
|
|
}
|
|
finally
|
|
{
|
|
if (haveDispatchDataLock)
|
|
{
|
|
// synchronize to force write ordering
|
|
synchronized (lockNode)
|
|
{
|
|
// null out refs to ensure they'll be GC'd
|
|
for (int i = 0; i < ancestorMax; i++)
|
|
{
|
|
ancestors [i] = null;
|
|
}
|
|
// notificationSet handled by notifyNode
|
|
|
|
dispatchDataLock = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void notifyNode(DomEvent e,
|
|
DomNode current,
|
|
boolean capture,
|
|
ListenerRecord[] notificationSet)
|
|
{
|
|
int count = 0;
|
|
Iterator iter;
|
|
|
|
iter = current.listeners.iterator();
|
|
|
|
// do any of this set of listeners get notified?
|
|
while (iter.hasNext())
|
|
{
|
|
ListenerRecord rec = (ListenerRecord)iter.next();
|
|
|
|
if (rec.useCapture != capture)
|
|
{
|
|
continue;
|
|
}
|
|
if (!e.type.equals (rec.type))
|
|
{
|
|
continue;
|
|
}
|
|
if (count >= notificationSet.length)
|
|
{
|
|
// very simple growth algorithm
|
|
int len = Math.max(notificationSet.length, 1);
|
|
ListenerRecord[] tmp = new ListenerRecord[len * 2];
|
|
System.arraycopy(notificationSet, 0, tmp, 0,
|
|
notificationSet.length);
|
|
notificationSet = tmp;
|
|
}
|
|
notificationSet[count++] = rec;
|
|
}
|
|
iter = null;
|
|
|
|
// Notify just those listeners
|
|
e.currentNode = current;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
try
|
|
{
|
|
iter = current.listeners.iterator();
|
|
// Late in the DOM CR process (3rd or 4th CR?) the
|
|
// removeEventListener spec became asymmetric with respect
|
|
// to addEventListener ... effect is now immediate.
|
|
while (iter.hasNext())
|
|
{
|
|
ListenerRecord rec = (ListenerRecord)iter.next();
|
|
|
|
if (rec.equals(notificationSet[i]))
|
|
{
|
|
notificationSet[i].listener.handleEvent(e);
|
|
break;
|
|
}
|
|
}
|
|
iter = null;
|
|
}
|
|
catch (Exception x)
|
|
{
|
|
// ignore all exceptions
|
|
}
|
|
notificationSet[i] = null; // free for GC
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L2 (Events)</b>
|
|
* Unregisters an event listener.
|
|
*/
|
|
public final void removeEventListener(String type,
|
|
EventListener listener,
|
|
boolean useCapture)
|
|
{
|
|
listeners.remove(new ListenerRecord(type, listener, useCapture));
|
|
nListeners = listeners.size();
|
|
// no exceptions reported
|
|
}
|
|
|
|
/**
|
|
* <b>DOM L1 (relocated in DOM L2)</b>
|
|
* In this node and all contained nodes (including attributes if
|
|
* relevant) merge adjacent text nodes. This is done while ignoring
|
|
* text which happens to use CDATA delimiters).
|
|
*/
|
|
public final void normalize()
|
|
{
|
|
// Suspend readonly status
|
|
boolean saved = readonly;
|
|
readonly = false;
|
|
for (DomNode ctx = first; ctx != null; ctx = ctx.next)
|
|
{
|
|
boolean saved2 = ctx.readonly;
|
|
ctx.readonly = false;
|
|
switch (ctx.nodeType)
|
|
{
|
|
case TEXT_NODE:
|
|
case CDATA_SECTION_NODE:
|
|
while (ctx.next != null &&
|
|
(ctx.next.nodeType == TEXT_NODE ||
|
|
ctx.next.nodeType == CDATA_SECTION_NODE))
|
|
{
|
|
Text text = (Text) ctx;
|
|
text.appendData(ctx.next.getNodeValue());
|
|
removeChild(ctx.next);
|
|
}
|
|
break;
|
|
case ELEMENT_NODE:
|
|
NamedNodeMap attrs = ctx.getAttributes();
|
|
int len = attrs.getLength();
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
DomNode attr = (DomNode) attrs.item(i);
|
|
boolean saved3 = attr.readonly;
|
|
attr.readonly = false;
|
|
attr.normalize();
|
|
attr.readonly = saved3;
|
|
}
|
|
// Fall through
|
|
case DOCUMENT_NODE:
|
|
case DOCUMENT_FRAGMENT_NODE:
|
|
case ATTRIBUTE_NODE:
|
|
case ENTITY_REFERENCE_NODE:
|
|
ctx.normalize();
|
|
break;
|
|
}
|
|
ctx.readonly = saved2;
|
|
}
|
|
readonly = saved;
|
|
}
|
|
|
|
/**
|
|
* Returns true iff node types match, and either (a) both nodes have no
|
|
* namespace and their getNodeName() values are the same, or (b) both
|
|
* nodes have the same getNamespaceURI() and same getLocalName() values.
|
|
*
|
|
* <p>Note that notion of a "Per-Element-Type" attribute name scope, as
|
|
* found in a non-normative appendix of the XML Namespaces specification,
|
|
* is not supported here. Your application must implement that notion,
|
|
* typically by not bothering to check nameAndTypeEquals for attributes
|
|
* without namespace URIs unless you already know their elements are
|
|
* nameAndTypeEquals.
|
|
*/
|
|
public boolean nameAndTypeEquals(Node other)
|
|
{
|
|
if (other == this)
|
|
{
|
|
return true;
|
|
}
|
|
// node types must match
|
|
if (nodeType != other.getNodeType())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// if both have namespaces, do a "full" comparision
|
|
// this is a "global" partition
|
|
String ns1 = this.getNamespaceURI();
|
|
String ns2 = other.getNamespaceURI();
|
|
|
|
if (ns1 != null && ns2 != null)
|
|
{
|
|
return ns1.equals(ns2) &&
|
|
equal(getLocalName(), other.getLocalName());
|
|
}
|
|
|
|
// if neither has a namespace, this is a "no-namespace" name.
|
|
if (ns1 == null && ns2 == null)
|
|
{
|
|
if (!getNodeName().equals(other.getNodeName()))
|
|
{
|
|
return false;
|
|
}
|
|
// can test the non-normative "per-element-type" scope here.
|
|
// if this is an attribute node and both nodes have been bound
|
|
// to elements (!!), then return the nameAndTypeEquals()
|
|
// comparison of those elements.
|
|
return true;
|
|
}
|
|
|
|
// otherwise they're unequal: one scoped, one not.
|
|
return false;
|
|
}
|
|
|
|
// DOM Level 3 methods
|
|
|
|
public String getBaseURI()
|
|
{
|
|
return (parent != null) ? parent.getBaseURI() : null;
|
|
}
|
|
|
|
public short compareDocumentPosition(Node other)
|
|
throws DOMException
|
|
{
|
|
return (short) compareTo(other);
|
|
}
|
|
|
|
/**
|
|
* DOM nodes have a natural ordering: document order.
|
|
*/
|
|
public final int compareTo(Object other)
|
|
{
|
|
if (other instanceof DomNode)
|
|
{
|
|
DomNode n1 = this;
|
|
DomNode n2 = (DomNode) other;
|
|
if (n1.owner != n2.owner)
|
|
{
|
|
return 0;
|
|
}
|
|
int d1 = n1.depth, d2 = n2.depth;
|
|
int delta = d1 - d2;
|
|
while (d1 > d2)
|
|
{
|
|
n1 = n1.parent;
|
|
d1--;
|
|
}
|
|
while (d2 > d1)
|
|
{
|
|
n2 = n2.parent;
|
|
d2--;
|
|
}
|
|
int c = compareTo2(n1, n2);
|
|
return (c != 0) ? c : delta;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Compare two nodes at the same depth.
|
|
*/
|
|
final int compareTo2(DomNode n1, DomNode n2)
|
|
{
|
|
if (n1 == n2 || n1.depth == 0 || n2.depth == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
int c = compareTo2(n1.parent, n2.parent);
|
|
return (c != 0) ? c : n1.index - n2.index;
|
|
}
|
|
|
|
public final String getTextContent()
|
|
throws DOMException
|
|
{
|
|
return getTextContent(true);
|
|
}
|
|
|
|
final String getTextContent(boolean topLevel)
|
|
throws DOMException
|
|
{
|
|
switch (nodeType)
|
|
{
|
|
case ELEMENT_NODE:
|
|
case ENTITY_NODE:
|
|
case ENTITY_REFERENCE_NODE:
|
|
case DOCUMENT_FRAGMENT_NODE:
|
|
CPStringBuilder buffer = new CPStringBuilder();
|
|
for (DomNode ctx = first; ctx != null; ctx = ctx.next)
|
|
{
|
|
String textContent = ctx.getTextContent(false);
|
|
if (textContent != null)
|
|
{
|
|
buffer.append(textContent);
|
|
}
|
|
}
|
|
return buffer.toString();
|
|
case TEXT_NODE:
|
|
case CDATA_SECTION_NODE:
|
|
if (((Text) this).isElementContentWhitespace())
|
|
{
|
|
return "";
|
|
}
|
|
return getNodeValue();
|
|
case ATTRIBUTE_NODE:
|
|
return getNodeValue();
|
|
case COMMENT_NODE:
|
|
case PROCESSING_INSTRUCTION_NODE:
|
|
return topLevel ? getNodeValue() : "";
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void setTextContent(String textContent)
|
|
throws DOMException
|
|
{
|
|
switch (nodeType)
|
|
{
|
|
case ELEMENT_NODE:
|
|
case ATTRIBUTE_NODE:
|
|
case ENTITY_NODE:
|
|
case ENTITY_REFERENCE_NODE:
|
|
case DOCUMENT_FRAGMENT_NODE:
|
|
for (DomNode ctx = first; ctx != null; )
|
|
{
|
|
DomNode n = ctx.next;
|
|
removeChild(ctx);
|
|
ctx = n;
|
|
}
|
|
if (textContent != null)
|
|
{
|
|
Text text = owner.createTextNode(textContent);
|
|
appendChild(text);
|
|
}
|
|
break;
|
|
case TEXT_NODE:
|
|
case CDATA_SECTION_NODE:
|
|
case COMMENT_NODE:
|
|
case PROCESSING_INSTRUCTION_NODE:
|
|
setNodeValue(textContent);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public boolean isSameNode(Node other)
|
|
{
|
|
return this == other;
|
|
}
|
|
|
|
public String lookupPrefix(String namespaceURI)
|
|
{
|
|
return (parent == null || parent == owner) ? null :
|
|
parent.lookupPrefix(namespaceURI);
|
|
}
|
|
|
|
public boolean isDefaultNamespace(String namespaceURI)
|
|
{
|
|
return (parent == null || parent == owner) ? false :
|
|
parent.isDefaultNamespace(namespaceURI);
|
|
}
|
|
|
|
public String lookupNamespaceURI(String prefix)
|
|
{
|
|
return (parent == null || parent == owner) ? null :
|
|
parent.lookupNamespaceURI(prefix);
|
|
}
|
|
|
|
public boolean isEqualNode(Node arg)
|
|
{
|
|
if (this == arg)
|
|
return true;
|
|
if (arg == null)
|
|
return false;
|
|
if (nodeType != arg.getNodeType())
|
|
return false;
|
|
switch (nodeType)
|
|
{
|
|
case ELEMENT_NODE:
|
|
case ATTRIBUTE_NODE:
|
|
if (!equal(getLocalName(), arg.getLocalName()) ||
|
|
!equal(getNamespaceURI(), arg.getNamespaceURI()))
|
|
return false;
|
|
break;
|
|
case PROCESSING_INSTRUCTION_NODE:
|
|
if (!equal(getNodeName(), arg.getNodeName()) ||
|
|
!equal(getNodeValue(), arg.getNodeValue()))
|
|
return false;
|
|
break;
|
|
case COMMENT_NODE:
|
|
case TEXT_NODE:
|
|
case CDATA_SECTION_NODE:
|
|
if (!equal(getNodeValue(), arg.getNodeValue()))
|
|
return false;
|
|
break;
|
|
}
|
|
// Children
|
|
Node argCtx = arg.getFirstChild();
|
|
getFirstChild(); // because of DomAttr lazy children
|
|
DomNode ctx = first;
|
|
for (; ctx != null && argCtx != null; ctx = ctx.next)
|
|
{
|
|
if (nodeType == DOCUMENT_NODE)
|
|
{
|
|
// Ignore whitespace outside document element
|
|
while (ctx != null && ctx.nodeType == TEXT_NODE)
|
|
ctx = ctx.next;
|
|
while (argCtx != null && ctx.getNodeType() == TEXT_NODE)
|
|
argCtx = argCtx.getNextSibling();
|
|
if (ctx == null && argCtx != null)
|
|
return false;
|
|
else if (argCtx == null && ctx != null)
|
|
return false;
|
|
}
|
|
if (!ctx.isEqualNode(argCtx))
|
|
return false;
|
|
argCtx = argCtx.getNextSibling();
|
|
}
|
|
if (ctx != null || argCtx != null)
|
|
return false;
|
|
|
|
// TODO DocumentType
|
|
return true;
|
|
}
|
|
|
|
boolean equal(String arg1, String arg2)
|
|
{
|
|
return ((arg1 == null && arg2 == null) ||
|
|
(arg1 != null && arg1.equals(arg2)));
|
|
}
|
|
|
|
public Object getFeature(String feature, String version)
|
|
{
|
|
DOMImplementation impl = (nodeType == DOCUMENT_NODE) ?
|
|
((Document) this).getImplementation() : owner.getImplementation();
|
|
if (impl.hasFeature(feature, version))
|
|
{
|
|
return this;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public Object setUserData(String key, Object data, UserDataHandler handler)
|
|
{
|
|
if (userData == null)
|
|
{
|
|
userData = new HashMap();
|
|
}
|
|
if (handler != null)
|
|
{
|
|
if (userDataHandlers == null)
|
|
{
|
|
userDataHandlers = new HashMap();
|
|
}
|
|
userDataHandlers.put(key, handler);
|
|
}
|
|
return userData.put(key, data);
|
|
}
|
|
|
|
public Object getUserData(String key)
|
|
{
|
|
if (userData == null)
|
|
{
|
|
return null;
|
|
}
|
|
return userData.get(key);
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
String nodeName = getNodeName();
|
|
String nodeValue = getNodeValue();
|
|
CPStringBuilder buf = new CPStringBuilder(getClass().getName());
|
|
buf.append('[');
|
|
if (nodeName != null)
|
|
{
|
|
buf.append(nodeName);
|
|
}
|
|
if (nodeValue != null)
|
|
{
|
|
if (nodeName != null)
|
|
{
|
|
buf.append('=');
|
|
}
|
|
buf.append('\'');
|
|
buf.append(encode(nodeValue));
|
|
buf.append('\'');
|
|
}
|
|
buf.append(']');
|
|
return buf.toString();
|
|
}
|
|
|
|
String encode(String value)
|
|
{
|
|
CPStringBuilder buf = null;
|
|
int len = value.length();
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
char c = value.charAt(i);
|
|
if (c == '\n')
|
|
{
|
|
if (buf == null)
|
|
{
|
|
buf = new CPStringBuilder(value.substring(0, i));
|
|
}
|
|
buf.append("\\n");
|
|
}
|
|
else if (c == '\r')
|
|
{
|
|
if (buf == null)
|
|
{
|
|
buf = new CPStringBuilder(value.substring(0, i));
|
|
}
|
|
buf.append("\\r");
|
|
}
|
|
else if (buf != null)
|
|
{
|
|
buf.append(c);
|
|
}
|
|
}
|
|
return (buf != null) ? buf.toString() : value;
|
|
}
|
|
|
|
String nodeTypeToString(short nodeType)
|
|
{
|
|
switch (nodeType)
|
|
{
|
|
case ELEMENT_NODE:
|
|
return "ELEMENT_NODE";
|
|
case ATTRIBUTE_NODE:
|
|
return "ATTRIBUTE_NODE";
|
|
case TEXT_NODE:
|
|
return "TEXT_NODE";
|
|
case CDATA_SECTION_NODE:
|
|
return "CDATA_SECTION_NODE";
|
|
case DOCUMENT_NODE:
|
|
return "DOCUMENT_NODE";
|
|
case DOCUMENT_TYPE_NODE:
|
|
return "DOCUMENT_TYPE_NODE";
|
|
case COMMENT_NODE:
|
|
return "COMMENT_NODE";
|
|
case PROCESSING_INSTRUCTION_NODE:
|
|
return "PROCESSING_INSTRUCTION_NODE";
|
|
case DOCUMENT_FRAGMENT_NODE:
|
|
return "DOCUMENT_FRAGMENT_NODE";
|
|
case ENTITY_NODE:
|
|
return "ENTITY_NODE";
|
|
case ENTITY_REFERENCE_NODE:
|
|
return "ENTITY_REFERENCE_NODE";
|
|
case NOTATION_NODE:
|
|
return "NOTATION_NODE";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
public void list(java.io.PrintStream out, int indent)
|
|
{
|
|
for (int i = 0; i < indent; i++)
|
|
out.print(" ");
|
|
out.println(toString());
|
|
for (DomNode ctx = first; ctx != null; ctx = ctx.next)
|
|
ctx.list(out, indent + 1);
|
|
}
|
|
|
|
}
|
|
|