Merge branch 'Jalview-BH/JAL-3026' into tasks/JAL-3033_jalviewjs_ant
authorJim Procter <jprocter@issues.jalview.org>
Mon, 25 Jun 2018 10:48:11 +0000 (11:48 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Mon, 25 Jun 2018 10:48:11 +0000 (11:48 +0100)
196 files changed:
.gitignore
.j2s [new file with mode: 0644]
README-BH.txt [new file with mode: 0644]
buildcore.xml [new file with mode: 0644]
coreclasses [new file with mode: 0644]
src/jalview/analysis/AlignmentSorter.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/Conservation.java
src/jalview/analysis/CrossRef.java
src/jalview/appletgui/PaintRefresher.java
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/bin/JalviewJS.java [new file with mode: 0644]
src/jalview/bin/JalviewJS2.java [new file with mode: 0644]
src/jalview/commands/EditCommand.java
src/jalview/datamodel/HiddenSequences.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/Desktop.java
src/jalview/gui/OverviewCanvas.java
src/jalview/gui/PaintRefresher.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SequenceFetcher.java
src/jalview/io/AppletFormatAdapter.java
src/jalview/io/DataSourceType.java
src/jalview/io/FileLoader.java
src/jalview/io/FileParse.java
src/jalview/io/IdentifyFile.java
src/jalview/io/vamsas/Tree.java
src/jalview/javascript/log4j/Appender.java [new file with mode: 0644]
src/jalview/javascript/log4j/ConsoleAppender.java [new file with mode: 0644]
src/jalview/javascript/log4j/Layout.java [new file with mode: 0644]
src/jalview/javascript/log4j/Level.java [new file with mode: 0644]
src/jalview/javascript/log4j/Logger.java [new file with mode: 0644]
src/jalview/javascript/log4j/Priority.java [new file with mode: 0644]
src/jalview/javascript/log4j/SimpleLayout.java [new file with mode: 0644]
src/jalview/javascript/log4j/WriterAppender.java [new file with mode: 0644]
src/jalview/javascript/log4j/spi/LoggingEvent.java [new file with mode: 0644]
src/jalview/javascript/log4j/spi/OptionHandler.java [new file with mode: 0644]
src/jalview/util/BrowserLauncher.java
src/jalview/util/MessageManager.java
src/jalview/util/Platform.java
src/jalview/ws/rest/params/SeqGroupIndexVector.java
src/javajs/J2SIgnoreImport.java [new file with mode: 0644]
src/javajs/J2SRequireImport.java [new file with mode: 0644]
src/javajs/_README.txt [new file with mode: 0644]
src/javajs/api/BytePoster.java [new file with mode: 0644]
src/javajs/api/EigenInterface.java [new file with mode: 0644]
src/javajs/api/GenericBinaryDocument.java [new file with mode: 0644]
src/javajs/api/GenericBinaryDocumentReader.java [new file with mode: 0644]
src/javajs/api/GenericCifDataParser.java [new file with mode: 0644]
src/javajs/api/GenericColor.java [new file with mode: 0644]
src/javajs/api/GenericImageDialog.java [new file with mode: 0644]
src/javajs/api/GenericImageEncoder.java [new file with mode: 0644]
src/javajs/api/GenericLineReader.java [new file with mode: 0644]
src/javajs/api/GenericOutputChannel.java [new file with mode: 0644]
src/javajs/api/GenericZipInputStream.java [new file with mode: 0644]
src/javajs/api/GenericZipTools.java [new file with mode: 0644]
src/javajs/api/Interface.java [new file with mode: 0644]
src/javajs/api/JSFunction.java [new file with mode: 0644]
src/javajs/api/JSInterface.java [new file with mode: 0644]
src/javajs/api/JSONEncodable.java [new file with mode: 0644]
src/javajs/api/ResettableStream.java [new file with mode: 0644]
src/javajs/api/ZInputStream.java [new file with mode: 0644]
src/javajs/api/js/J2SObjectInterface.java [new file with mode: 0644]
src/javajs/api/js/JSInterface.java [new file with mode: 0644]
src/javajs/export/PDFCreator.java [new file with mode: 0644]
src/javajs/export/PDFObject.java [new file with mode: 0644]
src/javajs/img/BMPDecoder.java [new file with mode: 0644]
src/javajs/img/CRCEncoder.java [new file with mode: 0644]
src/javajs/img/GifEncoder.java [new file with mode: 0644]
src/javajs/img/ImageEncoder.java [new file with mode: 0644]
src/javajs/img/Jpg64Encoder.java [new file with mode: 0644]
src/javajs/img/JpgEncoder.java [new file with mode: 0644]
src/javajs/img/PdfEncoder.java [new file with mode: 0644]
src/javajs/img/PngEncoder.java [new file with mode: 0644]
src/javajs/img/PpmEncoder.java [new file with mode: 0644]
src/javajs/util/A4.java [new file with mode: 0644]
src/javajs/util/AU.java [new file with mode: 0644]
src/javajs/util/AjaxURLConnection.java [new file with mode: 0644]
src/javajs/util/AjaxURLStreamHandler.java [new file with mode: 0644]
src/javajs/util/AjaxURLStreamHandlerFactory.java [new file with mode: 0644]
src/javajs/util/ArrayDataReader.java [new file with mode: 0644]
src/javajs/util/BArray.java [new file with mode: 0644]
src/javajs/util/BC.java [new file with mode: 0644]
src/javajs/util/BS.java [new file with mode: 0644]
src/javajs/util/Base64.java [new file with mode: 0644]
src/javajs/util/BinaryDocument.java [new file with mode: 0644]
src/javajs/util/CU.java [new file with mode: 0644]
src/javajs/util/CifDataParser.java [new file with mode: 0644]
src/javajs/util/CompoundDocDirEntry.java [new file with mode: 0644]
src/javajs/util/CompoundDocHeader.java [new file with mode: 0644]
src/javajs/util/CompoundDocument.java [new file with mode: 0644]
src/javajs/util/DF.java [new file with mode: 0644]
src/javajs/util/DataReader.java [new file with mode: 0644]
src/javajs/util/DebugJS.java [new file with mode: 0644]
src/javajs/util/Eigen.java [new file with mode: 0644]
src/javajs/util/Encoding.java [new file with mode: 0644]
src/javajs/util/JSAudioThread.java [new file with mode: 0644]
src/javajs/util/JSJSONParser.java [new file with mode: 0644]
src/javajs/util/JSONException.java [new file with mode: 0644]
src/javajs/util/JSThread.java [new file with mode: 0644]
src/javajs/util/LimitedLineReader.java [new file with mode: 0644]
src/javajs/util/ListDataReader.java [new file with mode: 0644]
src/javajs/util/Lst.java [new file with mode: 0644]
src/javajs/util/M3.java [new file with mode: 0644]
src/javajs/util/M34.java [new file with mode: 0644]
src/javajs/util/M4.java [new file with mode: 0644]
src/javajs/util/Matrix.java [new file with mode: 0644]
src/javajs/util/Measure.java [new file with mode: 0644]
src/javajs/util/MessagePackReader.java [new file with mode: 0644]
src/javajs/util/OC.java [new file with mode: 0644]
src/javajs/util/P3.java [new file with mode: 0644]
src/javajs/util/P3i.java [new file with mode: 0644]
src/javajs/util/P4.java [new file with mode: 0644]
src/javajs/util/PT.java [new file with mode: 0644]
src/javajs/util/Quat.java [new file with mode: 0644]
src/javajs/util/Rdr.java [new file with mode: 0644]
src/javajs/util/SB.java [new file with mode: 0644]
src/javajs/util/StringDataReader.java [new file with mode: 0644]
src/javajs/util/T3.java [new file with mode: 0644]
src/javajs/util/T3d.java [new file with mode: 0644]
src/javajs/util/T3i.java [new file with mode: 0644]
src/javajs/util/T4.java [new file with mode: 0644]
src/javajs/util/V3.java [new file with mode: 0644]
src/javajs/util/V3d.java [new file with mode: 0644]
src/javajs/util/XmlUtil.java [new file with mode: 0644]
src/javajs/util/ZipData.java [new file with mode: 0644]
src/javajs/util/ZipTools.java [new file with mode: 0644]
src/org/apache/harmony/luni/util/ExternalMessages.properties [new file with mode: 0644]
src/org/apache/harmony/luni/util/ExternalMessages.properties.js [new file with mode: 0644]
src/org/apache/harmony/luni/util/Msg.java [new file with mode: 0644]
src/org/apache/harmony/luni/util/MsgHelp.java [new file with mode: 0644]
src/org/apache/tools/bzip2/BZip2Constants.java [new file with mode: 0644]
src/org/apache/tools/bzip2/CBZip2InputStream.java [new file with mode: 0644]
src/org/apache/tools/bzip2/CBZip2InputStreamFactory.java [new file with mode: 0644]
src/org/apache/tools/bzip2/CRC.java [new file with mode: 0644]
src/org/json/simple/ItemList.java [new file with mode: 0644]
src/org/json/simple/JSONArray.java [new file with mode: 0644]
src/org/json/simple/JSONAware.java [new file with mode: 0644]
src/org/json/simple/JSONObject.java [new file with mode: 0644]
src/org/json/simple/JSONStreamAware.java [new file with mode: 0644]
src/org/json/simple/JSONValue.java [new file with mode: 0644]
src/org/json/simple/README.txt [new file with mode: 0644]
src/org/json/simple/parser/ContainerFactory.java [new file with mode: 0644]
src/org/json/simple/parser/ContentHandler.java [new file with mode: 0644]
src/org/json/simple/parser/JSONParser.java [new file with mode: 0644]
src/org/json/simple/parser/ParseException.java [new file with mode: 0644]
src/org/json/simple/parser/Yylex.java [new file with mode: 0644]
src/org/json/simple/parser/Yytoken.java [new file with mode: 0644]
src/org/xml/sax/AttributeList.java [new file with mode: 0644]
src/org/xml/sax/Attributes.java [new file with mode: 0644]
src/org/xml/sax/ContentHandler.java [new file with mode: 0644]
src/org/xml/sax/DTDHandler.java [new file with mode: 0644]
src/org/xml/sax/DocumentHandler.java [new file with mode: 0644]
src/org/xml/sax/EntityResolver.java [new file with mode: 0644]
src/org/xml/sax/ErrorHandler.java [new file with mode: 0644]
src/org/xml/sax/HandlerBase.java [new file with mode: 0644]
src/org/xml/sax/InputSource.java [new file with mode: 0644]
src/org/xml/sax/Locator.java [new file with mode: 0644]
src/org/xml/sax/Parser.java [new file with mode: 0644]
src/org/xml/sax/SAXException.java [new file with mode: 0644]
src/org/xml/sax/SAXNotRecognizedException.java [new file with mode: 0644]
src/org/xml/sax/SAXNotSupportedException.java [new file with mode: 0644]
src/org/xml/sax/SAXParseException.java [new file with mode: 0644]
src/org/xml/sax/XMLFilter.java [new file with mode: 0644]
src/org/xml/sax/XMLReader.java [new file with mode: 0644]
src/org/xml/sax/demo/ByteStreamDemo.java [new file with mode: 0644]
src/org/xml/sax/demo/CharacterStreamDemo.java [new file with mode: 0644]
src/org/xml/sax/demo/DemoHandler.java [new file with mode: 0644]
src/org/xml/sax/demo/EntityDemo.java [new file with mode: 0644]
src/org/xml/sax/ext/Attributes2.java [new file with mode: 0644]
src/org/xml/sax/ext/Attributes2Impl.java [new file with mode: 0644]
src/org/xml/sax/ext/DeclHandler.java [new file with mode: 0644]
src/org/xml/sax/ext/DefaultHandler2.java [new file with mode: 0644]
src/org/xml/sax/ext/EntityResolver2.java [new file with mode: 0644]
src/org/xml/sax/ext/LexicalHandler.java [new file with mode: 0644]
src/org/xml/sax/ext/Locator2.java [new file with mode: 0644]
src/org/xml/sax/ext/Locator2Impl.java [new file with mode: 0644]
src/org/xml/sax/helpers/AttributeListImpl.java [new file with mode: 0644]
src/org/xml/sax/helpers/AttributesImpl.java [new file with mode: 0644]
src/org/xml/sax/helpers/DefaultHandler.java [new file with mode: 0644]
src/org/xml/sax/helpers/LocatorImpl.java [new file with mode: 0644]
src/org/xml/sax/helpers/NamespaceSupport.java [new file with mode: 0644]
src/org/xml/sax/helpers/NewInstance.java [new file with mode: 0644]
src/org/xml/sax/helpers/ParserAdapter.java [new file with mode: 0644]
src/org/xml/sax/helpers/ParserFactory.java [new file with mode: 0644]
src/org/xml/sax/helpers/XMLFilterImpl.java [new file with mode: 0644]
src/org/xml/sax/helpers/XMLReaderAdapter.java [new file with mode: 0644]
src/org/xml/sax/helpers/XMLReaderFactory.java [new file with mode: 0644]
src/org/xml/sax/helpers/package.html [new file with mode: 0644]
swingjs/README.txt [new file with mode: 0644]
swingjs/SwingJS-site.zip [new file with mode: 0644]
swingjs/net.sf.j2s.core.jar [new file with mode: 0644]
template.html [new file with mode: 0644]
tools/ant-contrib.jar [new file with mode: 0644]
tools/closure_compiler.jar [new file with mode: 0644]

index 2a55560..012a802 100644 (file)
@@ -12,4 +12,6 @@
 TESTNG
 /jalviewApplet.jar
 /benchmarking/lib
-*.class
\ No newline at end of file
+*.class
+/site/
+/srcjar/
diff --git a/.j2s b/.j2s
new file mode 100644 (file)
index 0000000..10e5647
--- /dev/null
+++ b/.j2s
@@ -0,0 +1,3 @@
+j2s.compiler.status=enable
+j2s.class.replacements=org.apache.log4j.->jalview.javascript.log4j.;
+
diff --git a/README-BH.txt b/README-BH.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/buildcore.xml b/buildcore.xml
new file mode 100644 (file)
index 0000000..f4dff04
--- /dev/null
@@ -0,0 +1,60 @@
+<project name="JSmol" default="toJs" basedir=".">
+   <property name="site.path" value="site/swingjs" />
+   <property name="core.name" value="_jalview" />
+
+
+   <target name="toJs" id="toJs">
+                       
+       <!-- create a NON svn local directory only containing JS files
+       
+       <echo>Deleting the site directory.</echo>
+               <delete quiet="true" dir="site" />
+    -->
+       
+       <!-- make core files -->
+
+       <echo>creating and compressing core files - warnings are OK; "does not exist" is trouble</echo>
+       <echo>reading core class list from file coreclasses</echo>      
+       <loadresource property="coreclasses">
+       <file file="coreclasses"/>
+    </loadresource>
+    <antcall target="call-core">
+        <param name="call-core.name" value="${core.name}" />
+        <param name="call-core.list" value="
+                       core/coreswingjs.js
+               ${coreclasses}
+               " />
+    </antcall>
+    
+    <echo>......Now copy an html file in site/ and add   core:"core${core.name}",    to the Info block.</echo>         
+    
+  </target>
+
+
+         <target name="call-core" id="call-core">
+               <echo>......Creating core${call-core.name}.js</echo>
+               <concat destfile="${site.path}/js/core/tmp.js">
+                       <filelist dir="${site.path}/j2s" files="${call-core.list}" />
+               </concat> 
+
+               <replace dir="${site.path}/js/core" includes="tmp.js" token="Clazz." value="Clazz_"/>
+               <replace dir="${site.path}/js/core" includes="tmp.js" token="Clazz__" value="Clazz._"/>
+               <echo>......Generating ${site.path}/j2s/core/core${call-core.name}.js</echo>    
+               <concat destfile="${site.path}/j2s/core/core${call-core.name}.js"><filelist dir="${site.path}/js" files="
+                       core/coretop2.js
+                       core/tmp.js
+                       core/corebottom2.js
+                       " />
+               </concat>
+               <echo>......Generating ${site.path}/j2s/core/core${call-core.name}.z.js</echo>  
+               <java jar="tools/closure_compiler.jar" fork="true" dir="${site.path}/j2s/core" failonerror="false">
+                       <arg line="--js core${call-core.name}.js --js_output_file core${call-core.name}.z.js" />
+           </java>
+               <delete quiet="true" file="${site.path}/js/core/tmp.js" />
+         </target>
+
+               
+       
+</project>
diff --git a/coreclasses b/coreclasses
new file mode 100644 (file)
index 0000000..40ff83b
--- /dev/null
@@ -0,0 +1,304 @@
+jalview/analysis/AAFrequency.js
+jalview/analysis/AlignSeq.js
+jalview/analysis/AnnotationSorter.js
+jalview/analysis/Conservation.js
+jalview/analysis/CrossRef.js
+jalview/analysis/scoremodels/DistanceScoreModel.js
+jalview/analysis/scoremodels/FeatureDistanceModel.js
+jalview/analysis/scoremodels/PIDModel.js
+jalview/analysis/scoremodels/ScoreMatrix.js
+jalview/analysis/scoremodels/ScoreModels.js
+jalview/analysis/scoremodels/SimilarityScoreModel.js
+jalview/api/AlignCalcManagerI.js
+jalview/api/AlignCalcWorkerI.js
+jalview/api/AlignViewControllerGuiI.js
+jalview/api/AlignViewControllerI.js
+jalview/api/AlignViewportI.js
+jalview/api/AlignmentViewPanel.js
+jalview/api/BuildDetailsI.js
+jalview/api/FeatureColourI.js
+jalview/api/FeatureRenderer.js
+jalview/api/FeaturesDisplayedI.js
+jalview/api/FeaturesSourceI.js
+jalview/api/OOMHandlerI.js
+jalview/api/SequenceRenderer.js
+jalview/api/StructureSelectionManagerProvider.js
+jalview/api/ViewStyleI.js
+jalview/api/analysis/PairwiseScoreModelI.js
+jalview/api/analysis/ScoreModelI.js
+jalview/bin/ArgsParser.js
+jalview/bin/BuildDetails.js
+jalview/bin/Cache.js
+jalview/bin/Jalview.js
+jalview/bin/JalviewJS.js
+jalview/controller/AlignViewController.js
+jalview/datamodel/ASequence.js
+jalview/datamodel/ASequenceI.js
+jalview/datamodel/Alignment.js
+jalview/datamodel/AlignmentAnnotation.js
+jalview/datamodel/AlignmentI.js
+jalview/datamodel/AnnotatedCollectionI.js
+jalview/datamodel/Annotation.js
+jalview/datamodel/ColumnSelection.js
+jalview/datamodel/ContiguousI.js
+jalview/datamodel/DBRefSource.js
+jalview/datamodel/HiddenColumns.js
+jalview/datamodel/HiddenColumnsCursor.js
+jalview/datamodel/HiddenCursorPosition.js
+jalview/datamodel/HiddenSequences.js
+jalview/datamodel/PDBEntry.js
+jalview/datamodel/Profile.js
+jalview/datamodel/ProfileI.js
+jalview/datamodel/Profiles.js
+jalview/datamodel/ProfilesI.js
+jalview/datamodel/Range.js
+jalview/datamodel/ResidueCount.js
+jalview/datamodel/SearchResults.js
+jalview/datamodel/SearchResultsI.js
+jalview/datamodel/Sequence.js
+jalview/datamodel/SequenceCollectionI.js
+jalview/datamodel/SequenceCursor.js
+jalview/datamodel/SequenceFeature.js
+jalview/datamodel/SequenceGroup.js
+jalview/datamodel/SequenceI.js
+jalview/datamodel/features/FeatureLocationI.js
+jalview/datamodel/features/FeatureStore.js
+jalview/datamodel/features/RangeComparator.js
+jalview/datamodel/features/SequenceFeatures.js
+jalview/datamodel/features/SequenceFeaturesI.js
+jalview/gui/AlignFrame.js
+jalview/gui/AlignViewport.js
+jalview/gui/AlignmentPanel.js
+jalview/gui/AnnotationLabels.js
+jalview/gui/AnnotationPanel.js
+jalview/gui/ColourMenuHelper.js
+jalview/gui/Desktop.js
+jalview/gui/FeatureRenderer.js
+jalview/gui/IProgressIndicator.js
+jalview/gui/IdCanvas.js
+jalview/gui/IdPanel.js
+jalview/gui/IdwidthAdjuster.js
+jalview/gui/JvSwingUtils.js
+jalview/gui/PaintRefresher.js
+jalview/gui/ProgressBar.js
+jalview/gui/ScalePanel.js
+jalview/gui/SeqCanvas.js
+jalview/gui/SeqPanel.js
+jalview/gui/SequenceRenderer.js
+jalview/gui/ViewSelectionMenu.js
+jalview/io/AlignFile.js
+jalview/io/AlignmentFileReaderI.js
+jalview/io/AlignmentFileWriterI.js
+jalview/io/AppletFormatAdapter.js
+jalview/io/DataSourceType.js
+jalview/io/FastaFile.js
+jalview/io/FeaturesFile.js
+jalview/io/FileFormat.js
+jalview/io/FileFormatI.js
+jalview/io/FileFormats.js
+jalview/io/FileLoader.js
+jalview/io/FileParse.js
+jalview/io/FormatAdapter.js
+jalview/io/IdentifyFile.js
+jalview/io/PIRFile.js
+jalview/io/ScoreMatrixFile.js
+jalview/io/SequenceAnnotationReport.js
+jalview/javascript/log4j/Level.js
+jalview/javascript/log4j/Logger.js
+jalview/javascript/log4j/Priority.js
+jalview/jbgui/GAlignFrame.js
+jalview/jbgui/GAlignmentPanel.js
+jalview/jbgui/GDesktop.js
+jalview/renderer/AnnotationRenderer.js
+jalview/renderer/AwtRenderPanelI.js
+jalview/renderer/ResidueColourFinder.js
+jalview/renderer/ResidueShader.js
+jalview/renderer/ResidueShaderI.js
+jalview/renderer/ScaleRenderer.js
+jalview/renderer/seqfeatures/FeatureRenderer.js
+jalview/schemes/Blosum62ColourScheme.js
+jalview/schemes/BuriedColourScheme.js
+jalview/schemes/ClustalxColourScheme.js
+jalview/schemes/ColourSchemeI.js
+jalview/schemes/ColourSchemeProperty.js
+jalview/schemes/ColourSchemes.js
+jalview/schemes/Consensus.js
+jalview/schemes/FeatureColour.js
+jalview/schemes/HelixColourScheme.js
+jalview/schemes/HydrophobicColourScheme.js
+jalview/schemes/JalviewColourScheme.js
+jalview/schemes/NucleotideColourScheme.js
+jalview/schemes/PIDColourScheme.js
+jalview/schemes/PurinePyrimidineColourScheme.js
+jalview/schemes/RNAHelicesColour.js
+jalview/schemes/ResidueColourScheme.js
+jalview/schemes/ResidueProperties.js
+jalview/schemes/ScoreColourScheme.js
+jalview/schemes/StrandColourScheme.js
+jalview/schemes/TCoffeeColourScheme.js
+jalview/schemes/TaylorColourScheme.js
+jalview/schemes/TurnColourScheme.js
+jalview/schemes/ZappoColourScheme.js
+jalview/structure/CommandListener.js
+jalview/structure/SelectionListener.js
+jalview/structure/SelectionSource.js
+jalview/structure/SequenceListener.js
+jalview/structure/StructureImportSettings.js
+jalview/structure/StructureSelectionManager.js
+jalview/structure/VamsasSource.js
+jalview/urls/IdOrgSettings.js
+jalview/util/ColorUtils.js
+jalview/util/Comparison.js
+jalview/util/DBRefUtils.js
+jalview/util/Format.js
+jalview/util/LinkedIdentityHashSet.js
+jalview/util/MessageManager.js
+jalview/util/ParseHtmlBodyAndLinks.js
+jalview/util/Platform.js
+jalview/util/StringUtils.js
+jalview/viewmodel/AlignmentViewport.js
+jalview/viewmodel/ViewportListenerI.js
+jalview/viewmodel/ViewportProperties.js
+jalview/viewmodel/ViewportRanges.js
+jalview/viewmodel/seqfeatures/FeatureRendererModel.js
+jalview/viewmodel/seqfeatures/FeaturesDisplayed.js
+jalview/viewmodel/styles/ViewStyle.js
+jalview/workers/AlignCalcManager.js
+jalview/workers/AlignCalcWorker.js
+jalview/workers/ConsensusThread.js
+jalview/workers/ConservationThread.js
+jalview/ws/sifts/SiftsSettings.js
+java/awt/AWTKeyStroke.js
+java/awt/AlphaComposite.js
+java/awt/Composite.js
+java/awt/GridLayout.js
+java/awt/IllegalComponentStateException.js
+java/awt/Image.js
+java/awt/datatransfer/ClipboardOwner.js
+java/awt/dnd/DropTargetListener.js
+java/awt/event/ActionEvent.js
+java/awt/event/ComponentAdapter.js
+java/awt/event/FocusAdapter.js
+java/awt/event/KeyAdapter.js
+java/awt/event/MouseAdapter.js
+java/awt/event/MouseMotionAdapter.js
+java/awt/image/BufferedImage.js
+java/awt/image/ColorModel.js
+java/awt/image/DataBuffer.js
+java/awt/image/DataBufferInt.js
+java/awt/image/DirectColorModel.js
+java/awt/image/PackedColorModel.js
+java/awt/image/Raster.js
+java/awt/image/RenderedImage.js
+java/awt/image/SampleModel.js
+java/awt/image/SinglePixelPackedSampleModel.js
+java/awt/image/WritableRaster.js
+java/awt/print/Printable.js
+java/io/BufferedInputStream.js
+java/io/BufferedReader.js
+java/io/ByteArrayInputStream.js
+java/io/File.js
+java/io/FileDescriptor.js
+java/io/FileInputStream.js
+java/io/FileSystem.js
+java/io/FilterInputStream.js
+java/io/InputStream.js
+java/io/InputStreamReader.js
+java/io/Reader.js
+java/lang/Readable.js
+java/lang/Runtime.js
+java/lang/StringBuilder.js
+java/math/RoundingMode.js
+java/net/MalformedURLException.js
+java/net/URLConnection.js
+java/net/URLStreamHandler.js
+java/text/AttributedCharacterIterator.js
+java/text/CharacterIterator.js
+java/text/DateFormat.js
+java/text/DateFormatSymbols.js
+java/text/DecimalFormat.js
+java/text/DecimalFormatSymbols.js
+java/text/DigitList.js
+java/text/FieldPosition.js
+java/text/Format.js
+java/text/MessageFormat.js
+java/text/NumberFormat.js
+java/text/SimpleDateFormat.js
+java/util/ArrayDeque.js
+java/util/BitSet.js
+java/util/Calendar.js
+java/util/Deque.js
+java/util/GregorianCalendar.js
+java/util/LinkedHashMap.js
+java/util/ListResourceBundle.js
+java/util/NavigableMap.js
+java/util/NavigableSet.js
+java/util/Objects.js
+java/util/Properties.js
+java/util/PropertyResourceBundle.js
+java/util/ResourceBundle.js
+java/util/SortedMap.js
+java/util/SortedSet.js
+java/util/StringTokenizer.js
+java/util/TimeZone.js
+java/util/TreeMap.js
+java/util/concurrent/ConcurrentHashMap.js
+java/util/concurrent/ConcurrentMap.js
+java/util/concurrent/atomic/AtomicBoolean.js
+java/util/concurrent/atomic/AtomicInteger.js
+java/util/concurrent/locks/Lock.js
+java/util/concurrent/locks/ReadWriteLock.js
+java/util/concurrent/locks/ReentrantReadWriteLock.js
+java/util/regex/Pattern.js
+javajs/api/GenericLineReader.js
+javajs/api/JSONEncodable.js
+javajs/util/AjaxURLStreamHandler.js
+javajs/util/BS.js
+javajs/util/Rdr.js
+javajs/util/SB.js
+javax/swing/ComponentInputMap.js
+javax/swing/InputMap.js
+javax/swing/JInternalFrame.js
+javax/swing/JScrollPane.js
+javax/swing/JTabbedPane.js
+javax/swing/JToolTip.js
+javax/swing/JViewport.js
+javax/swing/JWindow.js
+javax/swing/KeyStroke.js
+javax/swing/Popup.js
+javax/swing/PopupFactory.js
+javax/swing/ScrollPaneConstants.js
+javax/swing/ScrollPaneLayout.js
+javax/swing/Scrollable.js
+javax/swing/Timer.js
+javax/swing/ToolTipManager.js
+javax/swing/ViewportLayout.js
+javax/swing/border/LineBorder.js
+javax/swing/event/MenuListener.js
+javax/swing/plaf/ComponentInputMapUIResource.js
+sun/awt/SunGraphicsCallback.js
+sun/awt/image/DataStealer.js
+sun/awt/image/IntegerComponentRaster.js
+sun/awt/image/IntegerInterleavedRaster.js
+sun/awt/image/SunWritableRaster.js
+sun/font/FontDesignMetrics.js
+sun/java2d/StateTrackable.js
+sun/java2d/StateTrackableDelegate.js
+sun/swing/DefaultLookup.js
+sun/text/resources/FormatData.js
+sun/text/resources/FormatData_en.js
+sun/util/calendar/AbstractCalendar.js
+sun/util/calendar/BaseCalendar.js
+sun/util/calendar/CalendarDate.js
+sun/util/calendar/CalendarSystem.js
+sun/util/calendar/CalendarUtils.js
+sun/util/calendar/Gregorian.js
+sun/util/calendar/ZoneInfo.js
+sun/util/resources/LocaleData.js
+swingjs/plaf/JSAppletUI.js
+swingjs/plaf/JSDesktopIconUI.js
+swingjs/plaf/JSInternalFrameUI.js
+swingjs/plaf/JSScrollPaneUI.js
+swingjs/plaf/JSTabbedPaneUI.js
+swingjs/plaf/JSToolTipUI.js
+swingjs/plaf/JSViewportUI.js
index b5cefe0..7ecce49 100755 (executable)
@@ -143,8 +143,8 @@ public class AlignmentSorter
     }
 
     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
-    List<SequenceI> asq;
-    synchronized (asq = align.getSequences())
+    List<SequenceI> asq = align.getSequences();
+    synchronized (asq)
     {
       for (int i = 0; i < len; i++)
       {
@@ -179,10 +179,10 @@ public class AlignmentSorter
   public static void setOrder(AlignmentI align, SequenceI[] seqs)
   {
     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
-    List<SequenceI> algn;
-    synchronized (algn = align.getSequences())
+    List<SequenceI> algn = align.getSequences();
+    synchronized (algn)
     {
-      List<SequenceI> tmp = new ArrayList<SequenceI>();
+      List<SequenceI> tmp = new ArrayList<>();
 
       for (int i = 0; i < seqs.length; i++)
       {
@@ -279,7 +279,7 @@ public class AlignmentSorter
   {
     // MAINTAINS ORIGNAL SEQUENCE ORDER,
     // ORDERS BY GROUP SIZE
-    List<SequenceGroup> groups = new ArrayList<SequenceGroup>();
+    List<SequenceGroup> groups = new ArrayList<>();
 
     if (groups.hashCode() != lastGroupHash)
     {
@@ -315,7 +315,7 @@ public class AlignmentSorter
 
     // NOW ADD SEQUENCES MAINTAINING ALIGNMENT ORDER
     // /////////////////////////////////////////////
-    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    List<SequenceI> seqs = new ArrayList<>();
 
     for (int i = 0; i < groups.size(); i++)
     {
@@ -357,7 +357,7 @@ public class AlignmentSorter
     // tmp2 = tmp.retainAll(mask);
     // return tmp2.addAll(mask.removeAll(tmp2))
 
-    ArrayList<SequenceI> seqs = new ArrayList<SequenceI>();
+    ArrayList<SequenceI> seqs = new ArrayList<>();
     int i, idx;
     boolean[] tmask = new boolean[mask.size()];
 
@@ -436,7 +436,7 @@ public class AlignmentSorter
   {
     int nSeq = align.getHeight();
 
-    List<SequenceI> tmp = new ArrayList<SequenceI>();
+    List<SequenceI> tmp = new ArrayList<>();
 
     tmp = _sortByTree(tree.getTopNode(), tmp, align.getSequences());
 
index d1217bf..14096cf 100644 (file)
@@ -2434,11 +2434,11 @@ public class AlignmentUtils
     /*
      * variants in first codon base
      */
-    for (DnaVariant var : codonVariants[0])
+    for (DnaVariant dnavar : codonVariants[0])
     {
-      if (var.variant != null)
+      if (dnavar.variant != null)
       {
-        String alleles = (String) var.variant.getValue(Gff3Helper.ALLELES);
+        String alleles = (String) dnavar.variant.getValue(Gff3Helper.ALLELES);
         if (alleles != null)
         {
           for (String base : alleles.split(","))
@@ -2449,7 +2449,7 @@ public class AlignmentUtils
                       + base3.toLowerCase();
               String canonical = base1.toUpperCase() + base2.toLowerCase()
                       + base3.toLowerCase();
-              if (addPeptideVariant(peptide, peptidePos, residue, var,
+              if (addPeptideVariant(peptide, peptidePos, residue, dnavar,
                       codon, canonical))
               {
                 count++;
index 131b39c..279d309 100755 (executable)
@@ -30,6 +30,7 @@ import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.schemes.ResidueProperties;
 import jalview.util.Comparison;
+import jalview.util.Format;
 
 import java.awt.Color;
 import java.util.List;
@@ -54,6 +55,8 @@ public class Conservation
 
   private static final int GAP_INDEX = -1;
 
+  private static final Format FORMAT_3DP = new Format("%2.5f");
+
   SequenceI[] sequences;
 
   int start;
@@ -273,7 +276,7 @@ public class Conservation
        * or not conserved (-1)
        * Using TreeMap means properties are displayed in alphabetical order
        */
-      SortedMap<String, Integer> resultHash = new TreeMap<String, Integer>();
+      SortedMap<String, Integer> resultHash = new TreeMap<>();
       SymbolCounts symbolCounts = values.getSymbolCounts();
       char[] symbols = symbolCounts.symbols;
       int[] counts = symbolCounts.values;
@@ -567,7 +570,7 @@ public class Conservation
    */
   private void percentIdentity(ScoreMatrix sm)
   {
-    seqNums = new Vector<int[]>();
+    seqNums = new Vector<>();
     int i = 0, iSize = sequences.length;
     // Do we need to calculate this again?
     for (i = 0; i < iSize; i++)
@@ -622,7 +625,7 @@ public class Conservation
   protected void findQuality(int startCol, int endCol,
           ScoreMatrix scoreMatrix)
   {
-    quality = new Vector<Double>();
+    quality = new Vector<>();
 
     double max = -Double.MAX_VALUE;
     float[][] scores = scoreMatrix.getMatrix();
@@ -808,7 +811,8 @@ public class Conservation
         value = quality.elementAt(i).floatValue();
         float vprop = value - qmin;
         vprop /= qmax;
-        quality2.annotations[i] = new Annotation(" ", String.valueOf(value),
+        String description = FORMAT_3DP.form(value);
+        quality2.annotations[i] = new Annotation(" ", description,
                 ' ', value, new Color(minR + (maxR * vprop),
                         minG + (maxG * vprop), minB + (maxB * vprop)));
       }
index e6bae9b..0e7662b 100644 (file)
@@ -99,7 +99,7 @@ public class CrossRef
    */
   public List<String> findXrefSourcesForSequences(boolean dna)
   {
-    List<String> sources = new ArrayList<String>();
+    List<String> sources = new ArrayList<>();
     for (SequenceI seq : fromSeqs)
     {
       if (seq != null)
@@ -151,7 +151,7 @@ public class CrossRef
        * find sequence's direct (dna-to-dna, peptide-to-peptide) xrefs
        */
       DBRefEntry[] lrfs = DBRefUtils.selectDbRefs(fromDna, seq.getDBRefs());
-      List<SequenceI> foundSeqs = new ArrayList<SequenceI>();
+      List<SequenceI> foundSeqs = new ArrayList<>();
 
       /*
        * find sequences in the alignment which xref one of these DBRefs
@@ -218,7 +218,7 @@ public class CrossRef
   public Alignment findXrefSequences(String source, boolean fromDna)
   {
 
-    rseqs = new ArrayList<SequenceI>();
+    rseqs = new ArrayList<>();
     AlignedCodonFrame cf = new AlignedCodonFrame();
     matcher = new SequenceIdMatcher(dataset.getSequences());
 
@@ -430,8 +430,8 @@ public class CrossRef
     if (retrieved != null)
     {
       boolean addedXref = false;
-      List<SequenceI> newDsSeqs = new ArrayList<SequenceI>(),
-              doNotAdd = new ArrayList<SequenceI>();
+      List<SequenceI> newDsSeqs = new ArrayList<>(),
+              doNotAdd = new ArrayList<>();
 
       for (SequenceI retrievedSequence : retrieved)
       {
@@ -1008,8 +1008,8 @@ public class CrossRef
       System.err.println("Empty dataset sequence set - NO VECTOR");
       return false;
     }
-    List<SequenceI> ds;
-    synchronized (ds = dataset.getSequences())
+    List<SequenceI> ds = dataset.getSequences();
+    synchronized (ds)
     {
       for (SequenceI nxt : ds)
       {
index fe99187..ddf590e 100755 (executable)
@@ -52,7 +52,7 @@ public class PaintRefresher
   {
     if (components == null)
     {
-      components = new Hashtable<String, Vector<Component>>();
+      components = new Hashtable<>();
     }
 
     if (components.containsKey(seqSetId))
@@ -191,8 +191,8 @@ public class PaintRefresher
         {
           // TODO: the following does not trigger any recalculation of
           // height/etc, or maintain the dataset
-          List<SequenceI> alsq;
-          synchronized (alsq = comp.getSequences())
+          List<SequenceI> alsq = comp.getSequences();
+          synchronized (alsq)
           {
             alsq.add(i, a1[i]);
           }
index dcd6546..d374682 100755 (executable)
@@ -288,7 +288,8 @@ public class Cache
   /** Default file is ~/.jalview_properties */
   static String propertiesFile;
 
-  private static boolean propsAreReadOnly = false;
+  private static boolean propsAreReadOnly = /** @j2sNative true || */
+          false;
 
   public static void initLogger()
   {
@@ -399,7 +400,8 @@ public class Cache
     // LOAD THE AUTHORS FROM THE authors.props file
     try
     {
-      String authorDetails = "jar:"
+      String authorDetails = /** @j2sNative "xxx" || */
+              "jar:"
               .concat(Cache.class.getProtectionDomain().getCodeSource()
                       .getLocation().toString().concat("!/authors.props"));
 
@@ -421,7 +423,8 @@ public class Cache
     // VERSION MAY HAVE CHANGED SINCE LAST USING JALVIEW
     try
     {
-      String buildDetails = "jar:".concat(Cache.class.getProtectionDomain()
+      String buildDetails = /** @j2sNative "xxx" || */
+              "jar:".concat(Cache.class.getProtectionDomain()
               .getCodeSource().getLocation().toString()
               .concat("!/.build_properties"));
 
index 30620a1..0d7143d 100755 (executable)
@@ -77,6 +77,9 @@ import groovy.util.GroovyScriptEngine;
  */
 public class Jalview
 {
+
+  // BH 6/19/2018 starting to work on JS version - just discovering issues
+
   /*
    * singleton instance of this class
    */
@@ -86,8 +89,14 @@ public class Jalview
 
   public static AlignFrame currentAlignFrame;
 
+  public static boolean isJS = /** @j2sNative true || */ // BH 2018
+          false;
+
   static
   {
+
+    if (!isJS)
+    { // BH 2018
     // grab all the rights we can the JVM
     Policy.setPolicy(new Policy()
     {
@@ -104,6 +113,8 @@ public class Jalview
       {
       }
     });
+
+    }
   }
 
   /**
@@ -188,7 +199,12 @@ public class Jalview
    */
   void doMain(String[] args)
   {
-    System.setSecurityManager(null);
+
+    if (!isJS)
+    {
+      System.setSecurityManager(null);
+    }
+
     System.out
             .println("Java version: " + System.getProperty("java.version"));
     System.out.println(System.getProperty("os.arch") + " "
@@ -198,17 +214,6 @@ public class Jalview
     ArgsParser aparser = new ArgsParser(args);
     boolean headless = false;
 
-    if (aparser.contains("help") || aparser.contains("h"))
-    {
-      showUsage();
-      System.exit(0);
-    }
-    if (aparser.contains("nodisplay") || aparser.contains("nogui")
-            || aparser.contains("headless"))
-    {
-      System.setProperty("java.awt.headless", "true");
-      headless = true;
-    }
     String usrPropsFile = aparser.getValue("props");
     Cache.loadProperties(usrPropsFile); // must do this before
     if (usrPropsFile != null)
@@ -217,23 +222,41 @@ public class Jalview
               "CMD [-props " + usrPropsFile + "] executed successfully!");
     }
 
-    // anything else!
-
-    final String jabawsUrl = aparser.getValue("jabaws");
-    if (jabawsUrl != null)
+    /**
+     * BH 2018 ignoring this section for JS
+     * 
+     * @j2sNative
+     */
     {
-      try
+      if (aparser.contains("help") || aparser.contains("h"))
       {
-        Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
-        System.out.println(
-                "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
-      } catch (MalformedURLException e)
+        showUsage();
+        System.exit(0);
+      }
+      if (aparser.contains("nodisplay") || aparser.contains("nogui")
+              || aparser.contains("headless"))
       {
-        System.err.println(
-                "Invalid jabaws parameter: " + jabawsUrl + " ignored");
+        System.setProperty("java.awt.headless", "true");
+        headless = true;
+      }
+      // anything else!
+
+      final String jabawsUrl = aparser.getValue("jabaws");
+      if (jabawsUrl != null)
+      {
+        try
+        {
+          Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
+          System.out.println(
+                  "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
+        } catch (MalformedURLException e)
+        {
+          System.err.println(
+                  "Invalid jabaws parameter: " + jabawsUrl + " ignored");
+        }
       }
-    }
 
+    }
     String defs = aparser.getValue("setprop");
     while (defs != null)
     {
@@ -330,55 +353,65 @@ public class Jalview
       desktop = new Desktop();
       desktop.setInBatchMode(true); // indicate we are starting up
       desktop.setVisible(true);
-      desktop.startServiceDiscovery();
-      if (!aparser.contains("nousagestats"))
-      {
-        startUsageStats(desktop);
-      }
-      else
-      {
-        System.err.println("CMD [-nousagestats] executed successfully!");
-      }
 
-      if (!aparser.contains("noquestionnaire"))
+      /**
+       * BH 2018 JS bypass this section
+       * 
+       * @j2sNative
+       * 
+       */
       {
-        String url = aparser.getValue("questionnaire");
-        if (url != null)
+        desktop.startServiceDiscovery();
+        if (!aparser.contains("nousagestats"))
         {
-          // Start the desktop questionnaire prompter with the specified
-          // questionnaire
-          Cache.log.debug("Starting questionnaire url at " + url);
-          desktop.checkForQuestionnaire(url);
-          System.out.println(
-                  "CMD questionnaire[-" + url + "] executed successfully!");
+          startUsageStats(desktop);
         }
         else
         {
-          if (Cache.getProperty("NOQUESTIONNAIRES") == null)
+          System.err.println("CMD [-nousagestats] executed successfully!");
+        }
+
+        if (!aparser.contains("noquestionnaire"))
+        {
+          String url = aparser.getValue("questionnaire");
+          if (url != null)
           {
             // Start the desktop questionnaire prompter with the specified
             // questionnaire
-            // String defurl =
-            // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
-            // //
-            String defurl = "http://www.jalview.org/cgi-bin/questionnaire.pl";
-            Cache.log.debug(
-                    "Starting questionnaire with default url: " + defurl);
-            desktop.checkForQuestionnaire(defurl);
+            Cache.log.debug("Starting questionnaire url at " + url);
+            desktop.checkForQuestionnaire(url);
+            System.out.println("CMD questionnaire[-" + url
+                    + "] executed successfully!");
+          }
+          else
+          {
+            if (Cache.getProperty("NOQUESTIONNAIRES") == null)
+            {
+              // Start the desktop questionnaire prompter with the specified
+              // questionnaire
+              // String defurl =
+              // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
+              // //
+              String defurl = "http://www.jalview.org/cgi-bin/questionnaire.pl";
+              Cache.log.debug(
+                      "Starting questionnaire with default url: " + defurl);
+              desktop.checkForQuestionnaire(defurl);
+            }
           }
         }
-      }
-      else
-      {
-        System.err.println("CMD [-noquestionnaire] executed successfully!");
-      }
+        else
+        {
+          System.err
+                  .println("CMD [-noquestionnaire] executed successfully!");
+        }
 
-      if (!aparser.contains("nonews"))
-      {
-        desktop.checkForNews();
-      }
+        if (!aparser.contains("nonews"))
+        {
+          desktop.checkForNews();
+        }
 
-      BioJsHTMLOutput.updateBioJS();
+        BioJsHTMLOutput.updateBioJS();
+      }
     }
 
     String file = null, data = null;
@@ -498,7 +531,7 @@ public class Jalview
       }
       System.out.println("CMD [-open " + file + "] executed successfully!");
 
-      if (!file.startsWith("http://"))
+      if (!isJS && !file.startsWith("http://"))
       {
         if (!(new File(file)).exists())
         {
@@ -510,7 +543,7 @@ public class Jalview
         }
       }
 
-      protocol = AppletFormatAdapter.checkProtocol(file);
+        protocol = AppletFormatAdapter.checkProtocol(file);
 
       try
       {
@@ -741,7 +774,8 @@ public class Jalview
     // And the user
     // ////////////////////
 
-    if (!headless && file == null && vamsasImport == null
+    if (/** @j2sNative false && */ // BH 2018
+    !headless && file == null && vamsasImport == null
             && jalview.bin.Cache.getDefault("SHOW_STARTUP_FILE", true))
     {
       file = jalview.bin.Cache.getDefault("STARTUP_FILE",
diff --git a/src/jalview/bin/JalviewJS.java b/src/jalview/bin/JalviewJS.java
new file mode 100644 (file)
index 0000000..84a17a4
--- /dev/null
@@ -0,0 +1,321 @@
+package jalview.bin;
+
+import jalview.analysis.AlignmentUtils;
+import jalview.datamodel.AlignmentI;
+import jalview.gui.AlignFrame;
+import jalview.gui.SplitFrame;
+import jalview.io.DataSourceType;
+import jalview.io.FileFormatException;
+import jalview.io.FileFormatI;
+import jalview.io.FileLoader;
+import jalview.io.IdentifyFile;
+import jalview.structure.StructureSelectionManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JFrame;
+import javax.swing.JInternalFrame;
+
+/**
+ * Entry point for Jalview as Javascript. Expects parameter names as for the
+ * JalviewLite applet, formatted as for the Jalview application, for example
+ * 
+ * <pre>
+ *   JalviewJS file /examples/uniref50.fa features /examples/exampleFeatures.txt \
+ *       PDBFile "/examples/pdb1.txt seq1"
+ * </pre>
+ * 
+ * Note that (unlike the applet) parameter names are case sensitive
+ */
+// TODO or format as file=/examples/uniref50.fa (etc)?
+public class JalviewJS
+{
+
+  static
+  {
+    /**
+     * @j2sNative
+     * 
+     *            thisApplet.__Info.args =
+     *            ["file","examples/uniref50.fa","features",
+     *            "examples/exampleFeatures.txt",
+     *            "props","/Users/gmcarstairs/.jalview_properties"];
+     */
+
+  }
+
+  private static final String PARAM_FILE = "file";
+
+  private static final String PARAM_FILE2 = "file2";
+
+  private static final String PARAM_TREE = "tree";
+
+  private static final String PARAM_FEATURES = "features";
+
+  private static final String PARAM_ANNOTATIONS = "annotations";
+
+  private static final String PARAM_SHOW_ANNOTATION = "showAnnotation";
+
+  private static final String PARAM_PDBFILE = "PDBFile";
+
+  private static final String PARAM_PROPS = "props";
+
+  private Map<String, String> params;
+
+  private List<String> pdbFileParams;
+
+  public static void main(String[] args) throws Exception
+  {
+    new JalviewJS().doMain(args);
+  }
+
+  /**
+   * Parses parameters and shows the frame and any loaded panels
+   * 
+   * @throws FileFormatException
+   */
+  void doMain(String[] args) throws FileFormatException
+  {
+    loadParameters(args);
+    if (getParameter(PARAM_FILE) == null)
+    {
+      usage();
+    }
+    else
+    {
+      showFrame();
+    }
+  }
+
+  /**
+   * Answers the value of the given runtime parameter, or null if not provided
+   * 
+   * @param paramName
+   * @return
+   */
+  private String getParameter(String paramName)
+  {
+    return params.get(paramName);
+  }
+
+  /**
+   * Prints a chastising, yet helpful, error message on syserr
+   */
+  private void usage()
+  {
+    System.err.println(
+            "Usage: JalviewJS file <alignmentFile> [features <featuresFile>]");
+    System.err.println("See documentation for full parameter list");
+  }
+
+  /**
+   * Parses any supplied parameters. Note that (unlike for the applet),
+   * parameter names are case sensitive.
+   * 
+   * @param args
+   * 
+   * @see http://www.jalview.org/examples/index.html#appletParameters
+   */
+  void loadParameters(String[] args)
+  {
+    ArgsParser parser = new ArgsParser(args);
+    params = new HashMap<>();
+
+    // TODO javascript-friendly source of properties
+    Cache.loadProperties(parser.getValue(PARAM_PROPS));
+    loadParameter(parser, PARAM_FILE);
+    loadParameter(parser, PARAM_FILE2);
+    loadParameter(parser, PARAM_TREE);
+    loadParameter(parser, PARAM_FEATURES);
+    loadParameter(parser, PARAM_ANNOTATIONS);
+    loadParameter(parser, PARAM_SHOW_ANNOTATION);
+    pdbFileParams = loadPdbParameters(parser);
+  }
+
+  /**
+   * Reads one command line parameter value and saves it against the parameter
+   * name. Note the saved value is null if the parameter is not present.
+   * 
+   * @param parser
+   * @param param
+   */
+  protected void loadParameter(ArgsParser parser, String param)
+  {
+    params.put(param, parser.getValue(param));
+  }
+
+  /**
+   * Reads parameter PDBFile, PDBFile1, PDFile2, ... and saves the value(s) (if
+   * any)
+   * 
+   * @param parser
+   * @return
+   */
+  List<String> loadPdbParameters(ArgsParser parser)
+  {
+    List<String> values = new ArrayList<>();
+    String value = parser.getValue(PARAM_PDBFILE);
+    if (value != null)
+    {
+      values.add(value);
+    }
+    int i = 1;
+    while (true)
+    {
+      value = parser.getValue(PARAM_PDBFILE + String.valueOf(i));
+      if (value != null)
+      {
+        values.add(value);
+      }
+      else
+      {
+        break;
+      }
+    }
+    return values;
+  }
+
+  /**
+   * Constructs and displays a JFrame containing an alignment panel (and any
+   * additional panels depending on parameters supplied)
+   * 
+   * @throws FileFormatException
+   */
+  void showFrame() throws FileFormatException
+  {
+    JFrame frame = new JFrame(getParameter(PARAM_FILE));
+    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+    /*
+     * construct an AlignFrame (optionally with features)
+     */
+    AlignFrame alignFrame = createAlignFrame(PARAM_FILE);
+    loadFeatures(alignFrame, getParameter(PARAM_FEATURES));
+
+    JInternalFrame internalFrame = alignFrame;
+
+    /*
+     * convert to SplitFrame if a valid file2 is supplied
+     */
+    AlignFrame alignFrame2 = createAlignFrame(PARAM_FILE2);
+    if (alignFrame2 != null)
+    {
+      SplitFrame splitFrame = loadSplitFrame(alignFrame, alignFrame2);
+      if (splitFrame != null)
+      {
+        internalFrame = splitFrame;
+      }
+    }
+
+    /*
+     * move AlignFrame (or SplitFrame) menu bar and content pane to our frame
+     * TODO there may be a less obscure way to do this
+     */
+    frame.setContentPane(internalFrame.getContentPane());
+    frame.setJMenuBar(internalFrame.getJMenuBar());
+
+    // fudge so that dialogs can be opened with this frame as parent
+    // todo JAL-3031 also override Desktop.addInternalFrame etc
+    // Desktop.parent = frame.getContentPane();
+
+    frame.pack();
+    frame.setSize(AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+    frame.setVisible(true);
+  }
+
+  /**
+   * Constructs a SplitFrame if cdna-protein mappings can be made between the
+   * given alignment frames, else returns null. Any mappings made are registered
+   * with StructureSelectionManager to enable broadcast to listeners.
+   * 
+   * @param alignFrame
+   * @param alignFrame2
+   * @return
+   */
+  protected SplitFrame loadSplitFrame(AlignFrame alignFrame,
+          AlignFrame alignFrame2)
+  {
+    // code borrowed from AlignViewport.openLinkedAlignment
+    AlignmentI al1 = alignFrame.getViewport().getAlignment();
+    AlignmentI al2 = alignFrame2.getViewport().getAlignment();
+    boolean al1Nuc = al1.isNucleotide();
+    if (al1Nuc == al2.isNucleotide())
+    {
+      System.err.println("Can't make split frame as alignments are both "
+              + (al1Nuc ? "nucleotide" : "protein"));
+      return null;
+    }
+    AlignmentI protein = al1Nuc ? al2 : al1;
+    AlignmentI cdna = al1Nuc ? al1 : al2;
+    boolean mapped = AlignmentUtils.mapProteinAlignmentToCdna(protein,
+            cdna);
+    if (!mapped)
+    {
+      System.err.println("Can't make any mappings for split frame");
+      return null;
+    }
+
+    /*
+     * register sequence mappings
+     */
+    StructureSelectionManager ssm = StructureSelectionManager
+            .getStructureSelectionManager(null);
+    ssm.registerMappings(protein.getCodonFrames());
+
+    cdna.alignAs(protein);
+
+    SplitFrame splitFrame = new SplitFrame(
+            al1Nuc ? alignFrame : alignFrame2,
+            al1Nuc ? alignFrame2 : alignFrame);
+
+    return splitFrame;
+  }
+
+  /**
+   * Loads on a features file if one was specified as a parameter
+   * 
+   * @param alignFrame
+   * @param featureFile
+   */
+  protected void loadFeatures(AlignFrame alignFrame, String featureFile)
+  {
+    if (featureFile != null)
+    {
+      // todo extract helper for protocol resolution from JalviewLite
+      DataSourceType sourceType = featureFile.startsWith("http")
+              ? DataSourceType.URL
+              : DataSourceType.RELATIVE_URL;
+      alignFrame.parseFeaturesFile(featureFile, sourceType);
+    }
+  }
+
+  /**
+   * Constructs and returns the frame containing the alignment and its
+   * annotations. Returns null if the specified parameter value is not set.
+   * 
+   * @param fileParam
+   * 
+   * @return
+   * @throws FileFormatException
+   */
+  AlignFrame createAlignFrame(String fileParam) throws FileFormatException
+  {
+    AlignFrame af = null;
+    String file = getParameter(fileParam);
+    if (file != null)
+    {
+      DataSourceType protocol = file.startsWith("http") ? DataSourceType.URL
+              : DataSourceType.RELATIVE_URL;
+      // DataSourceType protocol = AppletFormatAdapter.checkProtocol(file);
+      FileFormatI format = new IdentifyFile().identify(file, protocol);
+      FileLoader fileLoader = new FileLoader(false);
+      af = fileLoader.LoadFileWaitTillLoaded(file, protocol, format);
+    }
+
+    return af;
+  }
+
+}
diff --git a/src/jalview/bin/JalviewJS2.java b/src/jalview/bin/JalviewJS2.java
new file mode 100644 (file)
index 0000000..037f41e
--- /dev/null
@@ -0,0 +1,31 @@
+package jalview.bin;
+
+/**
+ * Entry point for JalviewJS development. 
+ * 
+ * 
+ * 
+ * @author RM
+ *
+ */
+public class JalviewJS2
+{
+
+  static {
+    /**
+     * @j2sNative
+     * 
+     *            thisApplet.__Info.args =
+     *            ["open","http://www.jalview.org/builds/release/examples/uniref50.fa","features",
+     *            "http://www.jalview.org/builds/release/examples/exampleFeatures.txt"];
+     */
+    
+  }
+
+  public static void main(String[] args) throws Exception
+  {
+    Jalview.main(args);
+  }
+
+
+}
index cac843f..b813dcf 100644 (file)
@@ -114,7 +114,7 @@ public class EditCommand implements CommandI
     public abstract Action getUndoAction();
   };
 
-  private List<Edit> edits = new ArrayList<Edit>();
+  private List<Edit> edits = new ArrayList<>();
 
   String description;
 
@@ -605,8 +605,8 @@ public class EditCommand implements CommandI
         // readd it to the alignment
         if (command.alIndex[i] < command.al.getHeight())
         {
-          List<SequenceI> sequences;
-          synchronized (sequences = command.al.getSequences())
+          List<SequenceI> sequences = command.al.getSequences();
+          synchronized (sequences)
           {
             if (!(command.alIndex[i] < 0))
             {
@@ -789,7 +789,7 @@ public class EditCommand implements CommandI
     if (modifyVisibility && !insert)
     {
       // only occurs if a sequence was added or deleted.
-      command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
+      command.deletedAnnotationRows = new Hashtable<>();
     }
     if (command.fullAlignmentHeight)
     {
@@ -948,7 +948,7 @@ public class EditCommand implements CommandI
 
     if (!insert)
     {
-      command.deletedAnnotations = new Hashtable<String, Annotation[]>();
+      command.deletedAnnotations = new Hashtable<>();
     }
 
     int aSize;
@@ -1138,7 +1138,7 @@ public class EditCommand implements CommandI
       return;
     }
 
-    List<SequenceFeature> oldsf = new ArrayList<SequenceFeature>();
+    List<SequenceFeature> oldsf = new ArrayList<>();
 
     int cSize = j - i;
 
@@ -1196,7 +1196,7 @@ public class EditCommand implements CommandI
 
     if (command.editedFeatures == null)
     {
-      command.editedFeatures = new Hashtable<SequenceI, List<SequenceFeature>>();
+      command.editedFeatures = new Hashtable<>();
     }
 
     command.editedFeatures.put(seq, oldsf);
@@ -1233,7 +1233,7 @@ public class EditCommand implements CommandI
    */
   public Map<SequenceI, SequenceI> priorState(boolean forUndo)
   {
-    Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
+    Map<SequenceI, SequenceI> result = new HashMap<>();
     if (getEdits() == null)
     {
       return result;
@@ -1266,7 +1266,7 @@ public class EditCommand implements CommandI
      * Work backwards through the edit list, deriving the sequences before each
      * was applied. The final result is the sequence set before any edits.
      */
-    Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
+    Iterator<Edit> editList = new ReverseListIterator<>(getEdits());
     while (editList.hasNext())
     {
       Edit oldEdit = editList.next();
@@ -1427,7 +1427,7 @@ public class EditCommand implements CommandI
     }
     else
     {
-      return new ReverseListIterator<Edit>(getEdits());
+      return new ReverseListIterator<>(getEdits());
     }
   }
 }
index c9dce08..b5efeb4 100755 (executable)
@@ -222,8 +222,8 @@ public class HiddenSequences
       end = hiddenSequences.length - 1;
     }
 
-    List<SequenceI> asequences;
-    synchronized (asequences = alignment.getSequences())
+    List<SequenceI> asequences = alignment.getSequences();
+    synchronized (asequences)
     {
       for (int index = end; index > start; index--)
       {
index 9de9e3b..a2b1e47 100644 (file)
@@ -378,7 +378,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     if (Desktop.desktop != null)
     {
       this.setDropTarget(new java.awt.dnd.DropTarget(this, this));
+      /**
+       * BH 2018 ignore service listeners
+       * 
+       * @j2sNative
+       * 
+       */
+      {
       addServiceListeners();
+      }
       setGUINucleotide();
     }
 
index 569257f..b4b0cdb 100644 (file)
@@ -382,65 +382,39 @@ public class Desktop extends jalview.jbgui.GDesktop
       setBounds((screenSize.width - 900) / 2, (screenSize.height - 650) / 2,
               900, 650);
     }
-    jconsole = new Console(this, showjconsole);
-    // add essential build information
-    jconsole.setHeader(
-            "Jalview Version: " + jalview.bin.Cache.getProperty("VERSION")
-                    + "\n" + "Jalview Installation: "
-                    + jalview.bin.Cache.getDefault("INSTALLATION",
-                            "unknown")
-                    + "\n" + "Build Date: "
-                    + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown")
-                    + "\n" + "Java version: "
-                    + System.getProperty("java.version") + "\n"
-                    + System.getProperty("os.arch") + " "
-                    + System.getProperty("os.name") + " "
-                    + System.getProperty("os.version"));
-
-    showConsole(showjconsole);
+    /**
+     * BH 2018
+     * 
+     * @j2sNative
+     */
+    {
 
-    showNews.setVisible(false);
+      jconsole = new Console(this, showjconsole);
+      // add essential build information
+      jconsole.setHeader("Jalview Version: "
+              + jalview.bin.Cache.getProperty("VERSION") + "\n"
+              + "Jalview Installation: "
+              + jalview.bin.Cache.getDefault("INSTALLATION", "unknown")
+              + "\n" + "Build Date: "
+              + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown") + "\n"
+              + "Java version: " + System.getProperty("java.version") + "\n"
+              + System.getProperty("os.arch") + " "
+              + System.getProperty("os.name") + " "
+              + System.getProperty("os.version"));
 
-    experimentalFeatures.setSelected(showExperimental());
+      showConsole(showjconsole);
 
-    getIdentifiersOrgData();
+      showNews.setVisible(false);
 
-    checkURLLinks();
+      experimentalFeatures.setSelected(showExperimental());
 
-    this.addWindowListener(new WindowAdapter()
-    {
-      @Override
-      public void windowClosing(WindowEvent evt)
-      {
-        quit();
-      }
-    });
+      getIdentifiersOrgData();
 
-    MouseAdapter ma;
-    this.addMouseListener(ma = new MouseAdapter()
-    {
-      @Override
-      public void mousePressed(MouseEvent evt)
-      {
-        if (evt.isPopupTrigger()) // Mac
-        {
-          showPasteMenu(evt.getX(), evt.getY());
-        }
-      }
-
-      @Override
-      public void mouseReleased(MouseEvent evt)
-      {
-        if (evt.isPopupTrigger()) // Windows
-        {
-          showPasteMenu(evt.getX(), evt.getY());
-        }
-      }
-    });
-    desktop.addMouseListener(ma);
+      checkURLLinks();
 
     this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
     // Spawn a thread that shows the splashscreen
+
     SwingUtilities.invokeLater(new Runnable()
     {
       @Override
@@ -478,6 +452,41 @@ public class Desktop extends jalview.jbgui.GDesktop
               }
 
             });
+
+    } // end BH 2018 ignore
+
+    this.addWindowListener(new WindowAdapter()
+    {
+      @Override
+      public void windowClosing(WindowEvent evt)
+      {
+        quit();
+      }
+    });
+
+    MouseAdapter ma;
+    this.addMouseListener(ma = new MouseAdapter()
+    {
+      @Override
+      public void mousePressed(MouseEvent evt)
+      {
+        if (evt.isPopupTrigger()) // Mac
+        {
+          showPasteMenu(evt.getX(), evt.getY());
+        }
+      }
+
+      @Override
+      public void mouseReleased(MouseEvent evt)
+      {
+        if (evt.isPopupTrigger()) // Windows
+        {
+          showPasteMenu(evt.getX(), evt.getY());
+        }
+      }
+    });
+    desktop.addMouseListener(ma);
+
   }
 
   /**
@@ -517,20 +526,28 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   public void checkForNews()
   {
+
+    /**
+     * BH 2018
+     * 
+     * @j2sNative
+     */
+    {
     final Desktop me = this;
     // Thread off the news reader, in case there are connection problems.
     addDialogThread(new Runnable()
     {
-      @Override
-      public void run()
-      {
-        Cache.log.debug("Starting news thread.");
+        @Override
+        public void run()
+        {
+          Cache.log.debug("Starting news thread.");
 
-        jvnews = new BlogReader(me);
-        showNews.setVisible(true);
-        Cache.log.debug("Completed news thread.");
-      }
+          jvnews = new BlogReader(me);
+          showNews.setVisible(true);
+          Cache.log.debug("Completed news thread.");
+        }
     });
+    }
   }
 
   public void getIdentifiersOrgData()
@@ -564,6 +581,12 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   void showNews(boolean visible)
   {
+    /**
+     * BH 2018
+     * 
+     * @j2sNative
+     * 
+     */
     {
       Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
       showNews.setSelected(visible);
@@ -647,7 +670,8 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   private void doVamsasClientCheck()
   {
-    if (jalview.bin.Cache.vamsasJarsPresent())
+    if (/** @j2sNative false && */ // BH 2018
+    jalview.bin.Cache.vamsasJarsPresent())
     {
       setupVamsasDisconnectedGui();
       VamsasMenu.setVisible(true);
@@ -856,7 +880,8 @@ public class Desktop extends jalview.jbgui.GDesktop
     frame.setResizable(resizable);
     frame.setMaximizable(resizable);
     frame.setIconifiable(resizable);
-    frame.setOpaque(false);
+    frame.setOpaque(/** @j2sNative true || */
+            false);
 
     if (frame.getX() < 1 && frame.getY() < 1)
     {
@@ -1143,8 +1168,8 @@ public class Desktop extends jalview.jbgui.GDesktop
     // for viewing
     JLabel label = new JLabel(
             MessageManager.getString("label.input_file_url"));
-    final JComboBox history = new JComboBox();
 
+    JComboBox history = new JComboBox();
     JPanel panel = new JPanel(new GridLayout(2, 1));
     panel.add(label);
     panel.add(history);
@@ -1166,66 +1191,28 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
     }
 
-    int reply = JvOptionPane.showInternalConfirmDialog(desktop, panel,
-            MessageManager.getString("label.input_alignment_from_url"),
-            JvOptionPane.OK_CANCEL_OPTION);
+    // BH 2018 -- providing a callback for SwingJS
+    // dialogOption is just a simple way to provide
+    // context for the modal-like response.
+    // The only requirement is that desktop implement
+    // PropertyChangeListener, which is used already in Java
+    // for changes in input value and such within the dialogs.
 
-    if (reply != JvOptionPane.OK_OPTION)
-    {
-      return;
-    }
+    String dialogOption = "label.input_alignment_from_url";
+    desktop.dialogData = new Object[] { dialogOption, viewport, history };
+    desktop.onDialogReturn(
+            JvOptionPane.showInternalConfirmDialog(desktop, panel,
+            MessageManager.getString(dialogOption),
+            JvOptionPane.OK_CANCEL_OPTION));
 
-    String url = history.getSelectedItem().toString();
+    // no code may follow this, as SwingJS will not block
+    // callback in JavaScript comes via a property change event,
+    // thus going into desktop.onDialogReturn(int) just the same as
+    // in Java.
 
-    if (url.toLowerCase().endsWith(".jar"))
-    {
-      if (viewport != null)
-      {
-        new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
-                FileFormat.Jalview);
-      }
-      else
-      {
-        new FileLoader().LoadFile(url, DataSourceType.URL,
-                FileFormat.Jalview);
-      }
-    }
-    else
-    {
-      FileFormatI format = null;
-      try
-      {
-        format = new IdentifyFile().identify(url, DataSourceType.URL);
-      } catch (FileFormatException e)
-      {
-        // TODO revise error handling, distinguish between
-        // URL not found and response not valid
-      }
-
-      if (format == null)
-      {
-        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-                MessageManager.formatMessage("label.couldnt_locate",
-                        new Object[]
-                        { url }),
-                MessageManager.getString("label.url_not_found"),
-                JvOptionPane.WARNING_MESSAGE);
-
-        return;
-      }
-
-      if (viewport != null)
-      {
-        new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
-                format);
-      }
-      else
-      {
-        new FileLoader().LoadFile(url, DataSourceType.URL, format);
-      }
-    }
   }
 
+
   /**
    * Opens the CutAndPaste window for the user to paste an alignment in to
    * 
@@ -1501,11 +1488,14 @@ public class Desktop extends jalview.jbgui.GDesktop
    */
   void showConsole(boolean selected)
   {
-    showConsole.setSelected(selected);
     // TODO: decide if we should update properties file
-    Cache.setProperty("SHOW_JAVA_CONSOLE",
-            Boolean.valueOf(selected).toString());
-    jconsole.setVisible(selected);
+    if (jconsole != null) // BH 2018
+    {
+      showConsole.setSelected(selected);
+      Cache.setProperty("SHOW_JAVA_CONSOLE",
+              Boolean.valueOf(selected).toString());
+      jconsole.setVisible(selected);
+    }
   }
 
   void reorderAssociatedWindows(boolean minimize, boolean close)
@@ -2365,7 +2355,8 @@ public class Desktop extends jalview.jbgui.GDesktop
       @Override
       public void run()
       {
-        if (Cache.getDefault("CHECKURLLINKS", true))
+        if (/** @j2sNative false && */ // BH 2018
+        Cache.getDefault("CHECKURLLINKS", true))
         {
           // check what the actual links are - if it's just the default don't
           // bother with the warning
@@ -2443,9 +2434,148 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @author AMW
    */
-  public class MyDesktopPane extends JDesktopPane implements Runnable
+  public class MyDesktopPane extends JDesktopPane
+          implements Runnable, PropertyChangeListener
   {
 
+    public Object[] dialogData;
+
+    // @Override
+    @Override
+    public void propertyChange(PropertyChangeEvent event)
+    {
+      Object val = event.getNewValue();
+      String name = event.getPropertyName();
+      System.out.println(name);
+      switch (event.getSource().getClass().getName())
+      {
+      case "javax.swing.JOptionPane":
+        switch (name)
+        {
+        case "inputValue":
+          onDialogReturn(val);
+          return;
+        case "value":
+          if (val instanceof Integer)
+          {
+            onDialogReturn(((Integer) val).intValue());
+          }
+          else
+          {
+            onDialogReturn(val);
+          }
+          return;
+        }
+        break;
+      case "javax.swing.ColorChooserDialog":
+        switch (name)
+        {
+        case "SelectedColor":
+          onDialogReturn(val);
+          return;
+        }
+        break;
+      case "javax.swing.JFileChooser":
+        switch (name)
+        {
+        case "SelectedFile":
+          File file = (File) val;
+          byte[] array = (val == null ? null
+                  : /** @j2sNative file._bytes || */
+                  null);
+          onDialogReturn("fileName is '" + file.getName() + "'\n\n"
+                  + new String(array));
+          return;
+        }
+        break;
+      }
+      System.out.println(event.getSource().getClass().getName() + " "
+              + event.getPropertyName() + ": " + event.getNewValue());
+    }
+
+    // JSCOmponent.DialogCaller interface
+    private void onDialogReturn(Object value)
+    {
+      System.out.println("not implemented");
+    }
+
+    // JSCOmponent.DialogCaller interface
+    void onDialogReturn(int value)
+    {
+      if (value != Math.floor(value))
+      {
+        // in JavaScript, this will be NaN, oddly enough
+        return;
+      }
+
+      switch ((String) dialogData[0])
+      {
+      case "label.input_alignment_from_url":
+        // reconstruct the parameter data
+        int reply = value;
+        AlignViewport viewport = (AlignViewport) dialogData[1];
+        JComboBox history = (JComboBox) dialogData[2];
+        // the rest of this is unchangaed
+        if (reply != JvOptionPane.OK_OPTION)
+        {
+          return;
+        }
+
+        String url = history.getSelectedItem().toString();
+
+        if (url.toLowerCase().endsWith(".jar"))
+        {
+          if (viewport != null)
+          {
+            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
+                    FileFormat.Jalview);
+          }
+          else
+          {
+            new FileLoader().LoadFile(url, DataSourceType.URL,
+                    FileFormat.Jalview);
+          }
+        }
+        else
+        {
+          FileFormatI format = null;
+          try
+          {
+            format = new IdentifyFile().identify(url, DataSourceType.URL);
+          } catch (FileFormatException e)
+          {
+            // TODO revise error handling, distinguish between
+            // URL not found and response not valid
+          }
+
+          if (format == null)
+          {
+            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+                    MessageManager.formatMessage("label.couldnt_locate",
+                            new Object[]
+                    { url }),
+                    MessageManager.getString("label.url_not_found"),
+                    JvOptionPane.WARNING_MESSAGE);
+
+            return;
+          }
+
+          if (viewport != null)
+          {
+            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
+                    format);
+          }
+          else
+          {
+            new FileLoader().LoadFile(url, DataSourceType.URL, format);
+          }
+        }
+
+        break;
+      }
+
+    }
+
     private static final float ONE_MB = 1048576f;
 
     boolean showMemoryUsage = false;
index cc361a5..ec2c49f 100644 (file)
@@ -31,9 +31,9 @@ import java.awt.Dimension;
 import java.awt.Graphics;
 import java.awt.image.BufferedImage;
 
-import javax.swing.JComponent;
+import javax.swing.JPanel;
 
-public class OverviewCanvas extends JComponent
+public class OverviewCanvas extends JPanel
 {
   private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
 
index ced5544..54fe488 100755 (executable)
@@ -39,7 +39,7 @@ import java.util.Map;
  */
 public class PaintRefresher
 {
-  static Map<String, List<Component>> components = new HashMap<String, List<Component>>();
+  static Map<String, List<Component>> components = new HashMap<>();
 
   /**
    * Add the given component to those registered under the given sequence set
@@ -60,7 +60,7 @@ public class PaintRefresher
     }
     else
     {
-      List<Component> vcoms = new ArrayList<Component>();
+      List<Component> vcoms = new ArrayList<>();
       vcoms.add(comp);
       components.put(seqSetId, vcoms);
     }
@@ -186,8 +186,8 @@ public class PaintRefresher
             System.err.println(
                     "IMPLEMENTATION PROBLEM: DATASET out of sync due to an insert whilst calling PaintRefresher.validateSequences(AlignmentI, ALignmentI)");
           }
-          List<SequenceI> alsq;
-          synchronized (alsq = comp.getSequences())
+          List<SequenceI> alsq = comp.getSequences();
+          synchronized (alsq)
           {
             alsq.add(i, a1[i]);
           }
@@ -240,7 +240,7 @@ public class PaintRefresher
     {
       return new AlignmentPanel[0];
     }
-    List<AlignmentPanel> tmp = new ArrayList<AlignmentPanel>();
+    List<AlignmentPanel> tmp = new ArrayList<>();
     for (Component comp : comps)
     {
       if (comp instanceof AlignmentPanel)
index 8f315bd..74af104 100755 (executable)
@@ -46,7 +46,7 @@ import java.beans.PropertyChangeEvent;
 import java.util.Iterator;
 import java.util.List;
 
-import javax.swing.JComponent;
+import javax.swing.JPanel;
 
 /**
  * The Swing component on which the alignment sequences, and annotations (if
@@ -54,7 +54,7 @@ import javax.swing.JComponent;
  * Wrapped mode, but not the scale above in Unwrapped mode.
  * 
  */
-public class SeqCanvas extends JComponent implements ViewportListenerI
+public class SeqCanvas extends JPanel implements ViewportListenerI
 {
   private static final String ZEROS = "0000000000";
 
index f545e70..0a51c7f 100755 (executable)
@@ -24,7 +24,6 @@ import jalview.api.FeatureSettingsModelI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.DBRefEntry;
-import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.fts.core.GFTSPanel;
 import jalview.fts.service.pdb.PDBFTSPanel;
@@ -1027,9 +1026,8 @@ public class SequenceFetcher extends JPanel implements Runnable
           // Alignments?
         }
 
-        SequenceFeature[] sfs = null;
-        List<SequenceI> alsqs;
-        synchronized (alsqs = al.getSequences())
+        List<SequenceI> alsqs = al.getSequences();
+        synchronized (alsqs)
         {
           for (SequenceI sq : alsqs)
           {
index 5e209e6..3f51ea8 100755 (executable)
@@ -422,6 +422,10 @@ public class AppletFormatAdapter
     {
       protocol = DataSourceType.URL;
     }
+    else if (jalview.bin.Jalview.isJS)
+    {
+      protocol = DataSourceType.RELATIVE_URL;
+    }
     else if (new File(data).exists())
     {
       protocol = DataSourceType.FILE;
index 5d0c462..7e2aeab 100644 (file)
@@ -22,5 +22,5 @@ package jalview.io;
 
 public enum DataSourceType
 {
-  FILE, URL, PASTE, CLASSLOADER;
+  FILE, URL, PASTE, CLASSLOADER, RELATIVE_URL;
 }
index f26d6da..9dd740b 100755 (executable)
@@ -176,32 +176,20 @@ public class FileLoader implements Runnable
   }
 
   /**
-   * start thread and wait until finished, then return the alignFrame that's
-   * (hopefully) been read.
+   * runs the 'run' method (in this thread), then return the alignFrame that's
+   * (hopefully) been read
    * 
    * @return
    */
   protected AlignFrame _LoadFileWaitTillLoaded()
   {
-    Thread loader = new Thread(this);
-    loader.start();
-
-    while (loader.isAlive())
-    {
-      try
-      {
-        Thread.sleep(500);
-      } catch (Exception ex)
-      {
-      }
-    }
-
+    this.run();
     return alignFrame;
   }
 
   public void updateRecentlyOpened()
   {
-    Vector recent = new Vector();
+    Vector<String> recent = new Vector<>();
     if (protocol == DataSourceType.PASTE)
     {
       // do nothing if the file was pasted in as text... there is no filename to
@@ -217,7 +205,7 @@ public class FileLoader implements Runnable
     String type = protocol == DataSourceType.FILE ? "RECENT_FILE"
             : "RECENT_URL";
 
-    String historyItems = jalview.bin.Cache.getProperty(type);
+    String historyItems = Cache.getProperty(type);
 
     StringTokenizer st;
 
@@ -227,7 +215,7 @@ public class FileLoader implements Runnable
 
       while (st.hasMoreTokens())
       {
-        recent.addElement(st.nextElement().toString().trim());
+        recent.addElement(st.nextToken().trim());
       }
     }
 
index 7117d0f..bf0a844 100755 (executable)
@@ -39,6 +39,8 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.zip.GZIPInputStream;
 
+import javajs.util.Rdr;
+
 /**
  * implements a random access wrapper around a particular datasource, for
  * passing to identifyFile and AlignFile objects.
@@ -339,6 +341,23 @@ public class FileParse
         }
       }
     }
+    else if (sourceType == DataSourceType.RELATIVE_URL)
+    {
+      String data = null;
+      /**
+       * BH 2018 hack for no support for access-origin
+       * 
+       * @j2sNative
+       * 
+       *            data = $.ajax({url:fileStr, async:false}).responseText;
+       * 
+       */
+
+      System.out.println(data);
+      dataIn = Rdr.getBR(data);
+      dataName = fileStr;
+
+    }
     else if (sourceType == DataSourceType.URL)
     {
       try
index ff959b0..aafe934 100755 (executable)
@@ -55,7 +55,7 @@ public class IdentifyFile
       }
     } catch (Exception e)
     {
-      System.err.println("Error whilst identifying");
+      System.err.println("Error whilst identifying " + file);
       e.printStackTrace(System.err);
       emessage = e.getMessage();
     }
index aa130cc..c0f65c4 100644 (file)
@@ -271,14 +271,14 @@ public class Tree extends DatastoreItem
    * @return vector of alignment sequences in order of SeqCigar array (but
    *         missing unfound seqcigars)
    */
-  private Vector findAlignmentSequences(AlignmentI jal,
+  private Vector<SequenceI> findAlignmentSequences(AlignmentI jal,
           SeqCigar[] sequences)
   {
     SeqCigar[] tseqs = new SeqCigar[sequences.length];
     System.arraycopy(sequences, 0, tseqs, 0, sequences.length);
-    Vector alsq = new Vector();
-    List<SequenceI> jalsqs;
-    synchronized (jalsqs = jal.getSequences())
+    Vector<SequenceI> alsq = new Vector<>();
+    List<SequenceI> jalsqs = jal.getSequences();
+    synchronized (jalsqs)
     {
       for (SequenceI asq : jalsqs)
       {
diff --git a/src/jalview/javascript/log4j/Appender.java b/src/jalview/javascript/log4j/Appender.java
new file mode 100644 (file)
index 0000000..1b2b676
--- /dev/null
@@ -0,0 +1,9 @@
+package jalview.javascript.log4j;
+
+import jalview.javascript.log4j.spi.LoggingEvent;
+
+public abstract class Appender
+{
+  public abstract void append(LoggingEvent loggingEvent);
+
+}
diff --git a/src/jalview/javascript/log4j/ConsoleAppender.java b/src/jalview/javascript/log4j/ConsoleAppender.java
new file mode 100644 (file)
index 0000000..f1aca45
--- /dev/null
@@ -0,0 +1,41 @@
+package jalview.javascript.log4j;
+
+import jalview.javascript.log4j.spi.LoggingEvent;
+
+public class ConsoleAppender
+{
+
+  private String name;
+
+  private Layout layout;
+
+
+  public ConsoleAppender()
+  {
+  }
+
+  public ConsoleAppender(Layout layout, String name)
+  {
+    this.layout = layout;
+    this.name = name;
+  }
+
+  public void setLayout(Layout layout)
+  {
+    this.layout = layout;
+  }
+
+  public void setName(String name)
+  {
+    this.name = name;
+  }
+
+  public void append(LoggingEvent event)
+  {
+
+    System.out
+            .println(event.getLevel() + ": " + event.getRenderedMessage());
+
+  }
+
+}
diff --git a/src/jalview/javascript/log4j/Layout.java b/src/jalview/javascript/log4j/Layout.java
new file mode 100644 (file)
index 0000000..ea79e1b
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.javascript.log4j;
+
+import jalview.javascript.log4j.spi.LoggingEvent;
+import jalview.javascript.log4j.spi.OptionHandler;
+
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.TTCCLayout;
+
+public abstract class Layout implements OptionHandler
+{
+
+  // Note that the line.separator property can be looked up even by
+  // applets.
+  public final static String LINE_SEP = System
+          .getProperty("line.separator");
+
+  public final static int LINE_SEP_LEN = LINE_SEP.length();
+
+  /**
+   * Implement this method to create your own layout format.
+   */
+  abstract public String format(LoggingEvent event);
+
+  /**
+   * Returns the content type output by this layout. The base class returns
+   * "text/plain".
+   */
+  public String getContentType()
+  {
+    return "text/plain";
+  }
+
+  /**
+   * Returns the header for the layout format. The base class returns
+   * <code>null</code>.
+   */
+  public String getHeader()
+  {
+    return null;
+  }
+
+  /**
+   * Returns the footer for the layout format. The base class returns
+   * <code>null</code>.
+   */
+  public String getFooter()
+  {
+    return null;
+  }
+
+  /**
+   * If the layout handles the throwable object contained within
+   * {@link LoggingEvent}, then the layout should return <code>false</code>.
+   * Otherwise, if the layout ignores throwable object, then the layout should
+   * return <code>true</code>. If ignoresThrowable is true, the appender is
+   * responsible for rendering the throwable.
+   * <p>
+   * The {@link SimpleLayout}, {@link TTCCLayout}, {@link PatternLayout} all
+   * return <code>true</code>. The {@link org.apache.log4j.xml.XMLLayout}
+   * returns <code>false</code>.
+   * 
+   * @since 0.8.4
+   */
+  abstract public boolean ignoresThrowable();
+
+}
diff --git a/src/jalview/javascript/log4j/Level.java b/src/jalview/javascript/log4j/Level.java
new file mode 100644 (file)
index 0000000..5691a47
--- /dev/null
@@ -0,0 +1,271 @@
+package jalview.javascript.log4j;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ * Defines the minimum set of levels recognized by the system, that is
+ * <code>OFF</code>, <code>FATAL</code>, <code>ERROR</code>, <code>WARN</code>,
+ * <code>INFO</code>, <code>DEBUG</code> and <code>ALL</code>.
+ * 
+ * <p>
+ * The <code>Level</code> class may be subclassed to define a larger level set.
+ * </p>
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public class Level extends Priority implements Serializable
+{
+
+  private static final String ALL_NAME = "ALL";
+
+  private static final String TRACE_NAME = "TRACE";
+
+  private static final String DEBUG_NAME = "DEBUG";
+
+  private static final String INFO_NAME = "INFO";
+
+  private static final String WARN_NAME = "WARN";
+
+  private static final String ERROR_NAME = "ERROR";
+
+  private static final String FATAL_NAME = "FATAL";
+
+  private static final String OFF_NAME = "OFF";
+
+  /**
+   * TRACE level integer value.
+   * 
+   * @since 1.2.12
+   */
+  public static final int TRACE_INT = 5000;
+
+  /**
+   * The <code>OFF</code> has the highest possible rank and is intended to turn
+   * off logging.
+   */
+  final static public Level OFF = new Level(OFF_INT, OFF_NAME, 0);
+
+  /**
+   * The <code>FATAL</code> level designates very severe error events that will
+   * presumably lead the application to abort.
+   */
+  final static public Level FATAL = new Level(FATAL_INT, FATAL_NAME, 0);
+
+  /**
+   * The <code>ERROR</code> level designates error events that might still allow
+   * the application to continue running.
+   */
+  final static public Level ERROR = new Level(ERROR_INT, ERROR_NAME, 3);
+
+  /**
+   * The <code>WARN</code> level designates potentially harmful situations.
+   */
+  final static public Level WARN = new Level(WARN_INT, WARN_NAME, 4);
+
+  /**
+   * The <code>INFO</code> level designates informational messages that
+   * highlight the progress of the application at coarse-grained level.
+   */
+  final static public Level INFO = new Level(INFO_INT, INFO_NAME, 6);
+
+  /**
+   * The <code>DEBUG</code> Level designates fine-grained informational events
+   * that are most useful to debug an application.
+   */
+  final static public Level DEBUG = new Level(DEBUG_INT, DEBUG_NAME, 7);
+
+  /**
+   * The <code>TRACE</code> Level designates finer-grained informational events
+   * than the <code>DEBUG</code level.
+   * 
+   * @since 1.2.12
+   */
+  public static final Level TRACE = new Level(TRACE_INT, TRACE_NAME, 7);
+
+  /**
+   * The <code>ALL</code> has the lowest possible rank and is intended to turn
+   * on all logging.
+   */
+  final static public Level ALL = new Level(ALL_INT, ALL_NAME, 7);
+
+  /**
+   * Serialization version id.
+   */
+  static final long serialVersionUID = 3491141966387921974L;
+
+  /**
+   * Instantiate a Level object.
+   */
+  protected Level(int level, String levelStr, int syslogEquivalent)
+  {
+    super(level, levelStr, syslogEquivalent);
+  }
+
+  /**
+   * Convert the string passed as argument to a level. If the conversion fails,
+   * then this method returns {@link #DEBUG}.
+   */
+  public static Level toLevel(String sArg)
+  {
+    return toLevel(sArg, Level.DEBUG);
+  }
+
+  /**
+   * Convert an integer passed as argument to a level. If the conversion fails,
+   * then this method returns {@link #DEBUG}.
+   */
+  public static Level toLevel(int val)
+  {
+    return toLevel(val, Level.DEBUG);
+  }
+
+  /**
+   * Convert an integer passed as argument to a level. If the conversion fails,
+   * then this method returns the specified default.
+   */
+  public static Level toLevel(int val, Level defaultLevel)
+  {
+    switch (val)
+    {
+    case ALL_INT:
+      return ALL;
+    case DEBUG_INT:
+      return Level.DEBUG;
+    case INFO_INT:
+      return Level.INFO;
+    case WARN_INT:
+      return Level.WARN;
+    case ERROR_INT:
+      return Level.ERROR;
+    case FATAL_INT:
+      return Level.FATAL;
+    case OFF_INT:
+      return OFF;
+    case TRACE_INT:
+      return Level.TRACE;
+    default:
+      return defaultLevel;
+    }
+  }
+
+  /**
+   * Convert the string passed as argument to a level. If the conversion fails,
+   * then this method returns the value of <code>defaultLevel</code>.
+   */
+  public static Level toLevel(String sArg, Level defaultLevel)
+  {
+    if (sArg == null)
+    {
+      return defaultLevel;
+    }
+    String s = sArg.toUpperCase();
+
+    if (s.equals(ALL_NAME))
+    {
+      return Level.ALL;
+    }
+    if (s.equals(DEBUG_NAME))
+    {
+      return Level.DEBUG;
+    }
+    if (s.equals(INFO_NAME))
+    {
+      return Level.INFO;
+    }
+    if (s.equals(WARN_NAME))
+    {
+      return Level.WARN;
+    }
+    if (s.equals(ERROR_NAME))
+    {
+      return Level.ERROR;
+    }
+    if (s.equals(FATAL_NAME))
+    {
+      return Level.FATAL;
+    }
+    if (s.equals(OFF_NAME))
+    {
+      return Level.OFF;
+    }
+    if (s.equals(TRACE_NAME))
+    {
+      return Level.TRACE;
+    }
+    //
+    // For Turkish i problem, see bug 40937
+    //
+    if (s.equals("\u0130NFO"))
+    {
+      return Level.INFO;
+    }
+    return defaultLevel;
+  }
+
+  /**
+   * Custom deserialization of Level.
+   * 
+   * @param s
+   *          serialization stream.
+   * @throws IOException
+   *           if IO exception.
+   * @throws ClassNotFoundException
+   *           if class not found.
+   */
+  private void readObject(final ObjectInputStream s)
+          throws IOException, ClassNotFoundException
+  {
+    s.defaultReadObject();
+    level = s.readInt();
+    syslogEquivalent = s.readInt();
+    levelStr = s.readUTF();
+    if (levelStr == null)
+    {
+      levelStr = "";
+    }
+  }
+
+  /**
+   * Serialize level.
+   * 
+   * @param s
+   *          serialization stream.
+   * @throws IOException
+   *           if exception during serialization.
+   */
+  private void writeObject(final ObjectOutputStream s) throws IOException
+  {
+    s.defaultWriteObject();
+    s.writeInt(level);
+    s.writeInt(syslogEquivalent);
+    s.writeUTF(levelStr);
+  }
+
+  /**
+   * Resolved deserialized level to one of the stock instances. May be overriden
+   * in classes derived from Level.
+   * 
+   * @return resolved object.
+   * @throws ObjectStreamException
+   *           if exception during resolution.
+   */
+  private Object readResolve() throws ObjectStreamException
+  {
+    //
+    // if the deserizalized object is exactly an instance of Level
+    //
+    if (getClass() == Level.class)
+    {
+      return toLevel(level);
+    }
+    //
+    // extension of Level can't substitute stock item
+    //
+    return this;
+  }
+
+}
diff --git a/src/jalview/javascript/log4j/Logger.java b/src/jalview/javascript/log4j/Logger.java
new file mode 100644 (file)
index 0000000..bb7eb34
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.javascript.log4j;
+
+import jalview.javascript.log4j.spi.LoggingEvent;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+public class Logger
+{
+
+  private static Map<String, Logger> registry;
+
+  private String name;
+
+  private Level level;
+
+  private boolean enabled = true;
+
+  private boolean isEnabled;
+
+  private Appender appender;
+
+  private Logger(String name)
+  {
+    this.name = name;
+  }
+
+  public static Logger getLogger(String name)
+  {
+    if (registry == null)
+    {
+      registry = new Hashtable<>();
+      getLogger("root");
+    }
+    Logger logger = registry.get(name);
+    if (logger == null)
+    {
+      registry.put(name, logger = new Logger(name));
+      logger.setLevel(Level.INFO);
+    }
+    return logger;
+  }
+
+  public static Logger getRootLogger()
+  {
+    return getLogger("root");
+  }
+
+  public void setLevel(Level l)
+  {
+    this.level = l;
+  }
+
+  public void addAppender(Appender appender)
+  {
+    this.appender = appender;
+  }
+
+  public boolean isDebugEnabled()
+  {
+    return isEnabled;
+  }
+
+  public void debug(Object o)
+  {
+    debug(o, null);
+  }
+
+  public void debug(Object o, Throwable e)
+  {
+    switch (level.level)
+    {
+    case Priority.FATAL_INT:
+    case Priority.ERROR_INT:
+    case Priority.WARN_INT:
+    case Priority.INFO_INT:
+    case Priority.DEBUG_INT:
+      log(o, e);
+      break;
+    }
+  }
+
+  public void info(Object o)
+  {
+    info(o, null);
+  }
+
+  public void info(Object o, Throwable e)
+  {
+    switch (level.level)
+    {
+    case Priority.FATAL_INT:
+    case Priority.ERROR_INT:
+    case Priority.WARN_INT:
+    case Priority.INFO_INT:
+      log(o, e);
+      break;
+    }
+
+  }
+
+  public void warn(Object o)
+  {
+    warn(o, null);
+  }
+
+  public void warn(Object o, Throwable e)
+  {
+    switch (level.level)
+    {
+    case Priority.FATAL_INT:
+    case Priority.ERROR_INT:
+    case Priority.WARN_INT:
+      log(o, e);
+      break;
+    }
+
+  }
+
+  public void error(Object o)
+  {
+    error(o, null);
+  }
+
+  public void error(Object o, Throwable e)
+  {
+    switch (level.level)
+    {
+    case Priority.FATAL_INT:
+    case Priority.ERROR_INT:
+      log(o, e);
+      break;
+    }
+
+  }
+
+  private void log(Object s, Throwable e)
+  {
+    switch (level.level)
+    {
+    case Priority.ERROR_INT:
+      if (appender == null)
+      {
+        System.err.println(s);
+        return;
+      }
+      break;
+    case Priority.WARN_INT:
+      if (appender == null)
+      {
+        System.err.println(s);
+        return;
+      }
+      break;
+    case Priority.INFO_INT:
+      if (appender == null)
+      {
+        System.out.println(s);
+        return;
+      }
+      break;
+    case Priority.DEBUG_INT:
+      if (appender == null)
+      {
+        System.out.println(s);
+        return;
+      }
+      break;
+    }
+    e.printStackTrace();
+    appender.append(new LoggingEvent(this, s.toString(), level));
+  }
+
+}
diff --git a/src/jalview/javascript/log4j/Priority.java b/src/jalview/javascript/log4j/Priority.java
new file mode 100644 (file)
index 0000000..2ace1eb
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors:  Kitching Simon <Simon.Kitching@orange.ch>
+
+package jalview.javascript.log4j;
+
+/**
+   <font color="#AA4444">Refrain from using this class directly, use
+   the {@link Level} class instead</font>.
+
+   @author Ceki G&uuml;lc&uuml; */
+public class Priority {
+
+  transient int level;
+  transient String levelStr;
+  transient int syslogEquivalent;
+
+  public final static int OFF_INT = Integer.MAX_VALUE;
+  public final static int FATAL_INT = 50000;
+  public final static int ERROR_INT = 40000;
+  public final static int WARN_INT  = 30000;
+  public final static int INFO_INT  = 20000;
+  public final static int DEBUG_INT = 10000;
+    //public final static int FINE_INT = DEBUG_INT;
+  public final static int ALL_INT = Integer.MIN_VALUE;
+
+  // too twisted for J2S -- Level class initializer initializes Priority, which
+  // creates a
+  // new Level before Priority is indicated to be a superclass of Level.
+  /**
+   * @deprecated Use {@link Level#FATAL} instead.
+   */
+  @Deprecated
+  final static public Priority FATAL = null;// new Level(FATAL_INT, "FATAL", 0);
+
+  /**
+   * @deprecated Use {@link Level#ERROR} instead.
+   */
+  @Deprecated
+  final static public Priority ERROR = null;// new Level(ERROR_INT, "ERROR", 3);
+
+  /**
+   * @deprecated Use {@link Level#WARN} instead.
+   */
+  @Deprecated
+  final static public Priority WARN = null;// new Level(WARN_INT, "WARN", 4);
+
+  /**
+   * @deprecated Use {@link Level#INFO} instead.
+   */
+  @Deprecated
+  final static public Priority INFO = null;// new Level(INFO_INT, "INFO", 6);
+
+  /**
+   * @deprecated Use {@link Level#DEBUG} instead.
+   */
+  @Deprecated
+  final static public Priority DEBUG = null;// new Level(DEBUG_INT, "DEBUG", 7);
+
+  /**
+    * Default constructor for deserialization.
+    */
+  protected Priority() {
+      level = DEBUG_INT;
+      levelStr = "DEBUG";
+      syslogEquivalent = 7;
+  }
+
+  /**
+     Instantiate a level object.
+   */
+  protected
+  Priority(int level, String levelStr, int syslogEquivalent) {
+    this.level = level;
+    this.levelStr = levelStr;
+    this.syslogEquivalent = syslogEquivalent;
+  }
+
+  /**
+     Two priorities are equal if their level fields are equal.
+     @since 1.2
+   */
+  @Override
+  public
+  boolean equals(Object o) {
+    if(o instanceof Priority) {
+      Priority r = (Priority) o;
+      return (this.level == r.level);
+    } else {
+      return false;
+    }
+  }
+
+  /**
+     Return the syslog equivalent of this priority as an integer.
+   */
+  public
+  final
+  int getSyslogEquivalent() {
+    return syslogEquivalent;
+  }
+
+
+   
+  /**
+     Returns <code>true</code> if this level has a higher or equal
+     level than the level passed as argument, <code>false</code>
+     otherwise.  
+     
+     <p>You should think twice before overriding the default
+     implementation of <code>isGreaterOrEqual</code> method.
+
+  */
+  public
+  boolean isGreaterOrEqual(Priority r) {
+    return level >= r.level;
+  }
+
+  // /**
+  // Return all possible priorities as an array of Level objects in
+  // descending order.
+  //
+  // @deprecated This method will be removed with no replacement.
+  // */
+  // public
+  // static
+  // Priority[] getAllPossiblePriorities() {
+  // return new Priority[] {Priority.FATAL, Priority.ERROR, Level.WARN,
+  // Priority.INFO, Priority.DEBUG};
+  // }
+
+
+  /**
+     Returns the string representation of this priority.
+   */
+  @Override
+  final
+  public
+  String toString() {
+    return levelStr;
+  }
+
+  /**
+     Returns the integer representation of this level.
+   */
+  public
+  final
+  int toInt() {
+    return level;
+  }
+
+  // /**
+  // * @deprecated Please use the {@link Level#toLevel(String)} method instead.
+  // */
+  // public
+  // static
+  // Priority toPriority(String sArg) {
+  // return Level.toLevel(sArg);
+  // }
+
+  // /**
+  // * @deprecated Please use the {@link Level#toLevel(int)} method instead.
+  // */
+  // public
+  // static
+  // Priority toPriority(int val) {
+  // return toPriority(val, Priority.DEBUG);
+  // }
+
+  // /**
+  // * @deprecated Please use the {@link Level#toLevel(int, Level)} method
+  // instead.
+  // */
+  // public
+  // static
+  // Priority toPriority(int val, Priority defaultPriority) {
+  // return Level.toLevel(val, (Level) defaultPriority);
+  // }
+  //
+  // /**
+  // * @deprecated Please use the {@link Level#toLevel(String, Level)} method
+  // instead.
+  // */
+  // public
+  // static
+  // Priority toPriority(String sArg, Priority defaultPriority) {
+  // return Level.toLevel(sArg, (Level) defaultPriority);
+  // }
+}
diff --git a/src/jalview/javascript/log4j/SimpleLayout.java b/src/jalview/javascript/log4j/SimpleLayout.java
new file mode 100644 (file)
index 0000000..9e5cbba
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.javascript.log4j;
+
+import jalview.javascript.log4j.spi.LoggingEvent;
+
+public class SimpleLayout extends Layout
+{
+
+  StringBuffer sbuf = new StringBuffer(128);
+
+  @Override
+  public void activateOptions()
+  {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public String format(LoggingEvent event)
+  {
+
+    sbuf.setLength(0);
+    sbuf.append(event.getLevel().toString());
+    sbuf.append(" - ");
+    sbuf.append(event.getRenderedMessage());
+    sbuf.append(LINE_SEP);
+    return sbuf.toString();
+  }
+  @Override
+  public boolean ignoresThrowable()
+  {
+    // TODO Auto-generated method stub
+    return false;
+  }
+
+}
diff --git a/src/jalview/javascript/log4j/WriterAppender.java b/src/jalview/javascript/log4j/WriterAppender.java
new file mode 100644 (file)
index 0000000..1a015f2
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jalview.javascript.log4j;
+
+public abstract class WriterAppender extends Appender
+{
+
+}
diff --git a/src/jalview/javascript/log4j/spi/LoggingEvent.java b/src/jalview/javascript/log4j/spi/LoggingEvent.java
new file mode 100644 (file)
index 0000000..669ae52
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.javascript.log4j.spi;
+
+import jalview.javascript.log4j.Logger;
+
+public class LoggingEvent
+{
+
+  private Object level;
+
+  private String msg;
+
+  private Logger logger;
+
+  public LoggingEvent(Logger logger, String level, Object message)
+  {
+    this.logger = logger;
+    this.level = level;
+    this.msg = message.toString();
+
+  }
+
+  public Object getLevel()
+  {
+    return level;
+  }
+
+  public String getRenderedMessage()
+  {
+    return msg;
+  }
+}
diff --git a/src/jalview/javascript/log4j/spi/OptionHandler.java b/src/jalview/javascript/log4j/spi/OptionHandler.java
new file mode 100644 (file)
index 0000000..9cd0f01
--- /dev/null
@@ -0,0 +1,8 @@
+package jalview.javascript.log4j.spi;
+
+public interface OptionHandler
+{
+
+  void activateOptions();
+
+}
index 0bc09cc..a55a65c 100755 (executable)
@@ -233,8 +233,15 @@ public class BrowserLauncher
    */
   static
   {
+
     loadedWithoutErrors = true;
 
+    /**
+     * 
+     * @j2sNative
+     * 
+     */
+    {
     String osName = System.getProperty("os.name");
 
     if (osName.startsWith("Mac OS"))
@@ -307,6 +314,7 @@ public class BrowserLauncher
     { // if we haven't hit any errors yet
       loadedWithoutErrors = loadClasses();
     }
+    }
   }
 
   /**
@@ -325,6 +333,12 @@ public class BrowserLauncher
    */
   private static boolean loadClasses()
   {
+
+    /**
+     * @j2sNative
+     * 
+     */
+    {
     switch (jvm)
     {
     case MRJ_2_0:
@@ -507,6 +521,7 @@ public class BrowserLauncher
       break;
     }
 
+    }
     return true;
   }
 
@@ -523,6 +538,11 @@ public class BrowserLauncher
    */
   private static Object locateBrowser()
   {
+    /**
+     * @j2sNative
+     * 
+     */
+    {
     if (browser != null)
     {
       return browser;
@@ -689,7 +709,10 @@ public class BrowserLauncher
       break;
     }
 
+    }
+
     return browser;
+
   }
 
   /**
@@ -711,6 +734,17 @@ public class BrowserLauncher
    */
   public static void openURL(String url) throws IOException
   {
+
+    /**
+     * @j2sNative
+     * 
+     *            window.open(url);
+     * 
+     * 
+     */
+
+    {
+
     if (!loadedWithoutErrors)
     {
       throw new IOException(MessageManager
@@ -896,8 +930,10 @@ public class BrowserLauncher
 
       break;
     }
+    }
   }
 
+
   /**
    * Methods required for Mac OS X. The presence of native methods does not
    * cause any problems on other platforms.
index 3494181..1cfe0c6 100644 (file)
@@ -23,8 +23,11 @@ package jalview.util;
 import java.text.MessageFormat;
 import java.util.Locale;
 import java.util.ResourceBundle;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import java.util.ResourceBundle.Control;
+//import java.util.logging.Level;
+//import java.util.logging.Logger;
+
+import org.apache.log4j.Logger;
 
 /**
  * 
@@ -36,6 +39,8 @@ import java.util.logging.Logger;
 public class MessageManager
 {
 
+  // BH 2018 switched to org.apache.llog4j.Logger
+
   private static ResourceBundle rb;
 
   private static Logger log = Logger
@@ -52,20 +57,21 @@ public class MessageManager
       // Locale.setDefault(loc);
       /* Getting messages for GV */
       log.info("Getting messages for lang: " + loc);
-      rb = ResourceBundle.getBundle("lang.Messages", loc);
-      if (log.isLoggable(Level.FINEST))
-      {
-        // this might take a while, so we only do it if it will be shown
-        log.finest("Language keys: " + rb.keySet());
-      }
+      Control control = Control.getControl(Control.FORMAT_PROPERTIES);
+      rb = ResourceBundle.getBundle("lang.Messages", loc, control);
+      // if (log.isLoggable(Level.FINEST))
+      // {
+      // // this might take a while, so we only do it if it will be shown
+      // log.info("Language keys: " + rb.keySet()); // was FINEST
+      // }
     } catch (Exception q)
     {
-      log.warning("Exception when initting Locale for i18n messages\n"
+      log.warn("Exception when initting Locale for i18n messages\n"
               + q.getMessage());
       q.printStackTrace();
     } catch (Error v)
     {
-      log.warning("Error when initting Locale for i18n messages\n"
+      log.warn("Error when initting Locale for i18n messages\n"
               + v.getMessage());
       v.printStackTrace();
     }
@@ -80,7 +86,7 @@ public class MessageManager
       value = rb.getString(key);
     } catch (Exception e)
     {
-      log.warning("I18N missing: " + loc + "\t" + key);
+      log.warn("I18N missing: " + loc + "\t" + key);
     }
     return value;
   }
@@ -119,8 +125,8 @@ public class MessageManager
       name = rb.getString(smkey);
     } catch (Exception x)
     {
-      log.finest("I18N missing key with root " + keyroot + ": " + loc + "\t"
-              + smkey);
+      log.info("I18N missing key with root " + keyroot + ": " + loc + "\t"
+              + smkey); // was FINEST
     }
     return name;
   }
index 2c74609..6a5f133 100644 (file)
@@ -30,6 +30,7 @@ import java.awt.event.MouseEvent;
  */
 public class Platform
 {
+
   private static Boolean isAMac = null, isWindows = null;
 
   private static Boolean isHeadless = null;
@@ -37,13 +38,16 @@ public class Platform
   /**
    * sorry folks - Macs really are different
    * 
+   * BH: disabled for SwingJS -- will need to check key-press issues
+   * 
    * @return true if we do things in a special way.
    */
   public static boolean isAMac()
   {
     if (isAMac == null)
     {
-      isAMac = System.getProperty("os.name").indexOf("Mac") > -1;
+      isAMac = /** @j2sNative false && */
+              System.getProperty("os.name").indexOf("Mac") > -1;
     }
 
     return isAMac.booleanValue();
@@ -59,7 +63,8 @@ public class Platform
   {
     if (isWindows == null)
     {
-      isWindows = System.getProperty("os.name").indexOf("Win") > -1;
+      isWindows = /** @j2sNative false && */
+              System.getProperty("os.name").indexOf("Win") > -1;
     }
     return isWindows.booleanValue();
   }
index dcb7fab..9210414 100644 (file)
@@ -75,6 +75,7 @@ public class SeqGroupIndexVector extends InputType
    *          - alignment to be processed
    * @return al or a new alignment with appropriate attributes/order for input
    */
+  @Override
   public AlignmentI prepareAlignment(AlignmentI al)
   {
     jalview.analysis.AlignmentSorter.sortByGroup(al);
@@ -90,10 +91,10 @@ public class SeqGroupIndexVector extends InputType
     AlignmentI al = rj.getAlignmentForInput(token, type);
     // assume that alignment is properly ordered so groups form consecutive
     // blocks
-    ArrayList<int[]> gl = new ArrayList<int[]>();
+    ArrayList<int[]> gl = new ArrayList<>();
     int p = 0, lowest = al.getHeight(), highest = 0;
-    List<SequenceGroup> sgs;
-    synchronized (sgs = al.getGroups())
+    List<SequenceGroup> sgs = al.getGroups();
+    synchronized (sgs)
     {
       for (SequenceGroup sg : sgs)
       {
@@ -125,9 +126,13 @@ public class SeqGroupIndexVector extends InputType
           else
           {
             if (p < se[0])
+            {
               se[0] = p;
+            }
             if (p > se[1])
+            {
               se[1] = p;
+            }
           }
         }
         if (se != null)
@@ -168,7 +173,9 @@ public class SeqGroupIndexVector extends InputType
     int[][] vals = gl.toArray(new int[gl.size()][]);
     int[] srt = new int[gl.size()];
     for (int i = 0; i < vals.length; i++)
+    {
       srt[i] = vals[i][0];
+    }
     jalview.util.QuickSort.sort(srt, vals);
     list = false;
     int last = vals[0][0] - 1;
@@ -210,7 +217,7 @@ public class SeqGroupIndexVector extends InputType
   @Override
   public List<String> getURLEncodedParameter()
   {
-    ArrayList<String> prms = new ArrayList<String>();
+    ArrayList<String> prms = new ArrayList<>();
     super.addBaseParams(prms);
     prms.add("minsize='" + minsize + "'");
     prms.add("sep='" + sep + "'");
@@ -243,7 +250,9 @@ public class SeqGroupIndexVector extends InputType
       {
         minsize = Integer.valueOf(val);
         if (minsize >= 0)
+        {
           return true;
+        }
       } catch (Exception x)
       {
 
diff --git a/src/javajs/J2SIgnoreImport.java b/src/javajs/J2SIgnoreImport.java
new file mode 100644 (file)
index 0000000..b92c82b
--- /dev/null
@@ -0,0 +1,7 @@
+package javajs;
+
+public @interface J2SIgnoreImport {
+
+       Class<?>[] value();
+
+}
diff --git a/src/javajs/J2SRequireImport.java b/src/javajs/J2SRequireImport.java
new file mode 100644 (file)
index 0000000..646cebd
--- /dev/null
@@ -0,0 +1,7 @@
+package javajs;
+
+public @interface J2SRequireImport {
+
+       Class<?>[] value();
+
+}
diff --git a/src/javajs/_README.txt b/src/javajs/_README.txt
new file mode 100644 (file)
index 0000000..e8c3779
--- /dev/null
@@ -0,0 +1,38 @@
+The javajs directory contains a variety of Java files that you are 
+encouraged to use in your project. It is self-contained, and the 
+classes and methods all work in Java as well as JavaScript. 
+
+These classes perform functions not standard in Java or provide
+classes that are particularly optimized for JavaScript (fewer overloaded 
+methods, especially). 
+
+-- creating (minimal) PDF files
+-- decoding and encoding image files
+-- working with binary, compound document, JSON, and zip data
+-- providing very efficient classes for vectors, matricies, and quaternions
+-- providing a java.lang.Thread subclass (JSThread) that can be used
+   wherever a thread is necessary; note that JSThread cannot sleep(), but
+   it provides a means of managing setTimeout callbacks that is very easy
+   to implement.
+-- providing two very important annotations:
+
+  @J2SIgnoreImport    Indicates that this class will never be used in JavaScript
+                      and thus does not need to be indicated as a dependency. In
+                      some cases, this annotation is necessary just to break a 
+                      cyclical dependency that Java somehow handles, but J2S does not.   
+   
+  @J2SRequireImport   Allows a SwingJS developer to work around J2S transpiler 
+                      issues that for some reason it is not indicating the need for 
+                      a dependent class. Typically this is because a static call
+                      to a static method in a class. 
+                      
+                      In addition, though, @J2SRequireImport can be used to "inject"
+                      JavaScript at a specific point in code loading. For example, 
+                      swingjs.plaf.JSSlider uses the following annotation to load 
+                      jQueryUI, fooling J2S to think that it is a "class" file. 
+                      
+                      @J2SRequireImport(swingjs.jquery.JQueryUI.class)
+                      
+
+All files in the javajs/util directory will be moved to j2s/JU by a build script.
+
diff --git a/src/javajs/api/BytePoster.java b/src/javajs/api/BytePoster.java
new file mode 100644 (file)
index 0000000..0b0f29a
--- /dev/null
@@ -0,0 +1,7 @@
+package javajs.api;
+
+public interface BytePoster {
+
+  String postByteArray(String fileName, byte[] bytes);
+
+}
diff --git a/src/javajs/api/EigenInterface.java b/src/javajs/api/EigenInterface.java
new file mode 100644 (file)
index 0000000..4a1b592
--- /dev/null
@@ -0,0 +1,15 @@
+package javajs.api;
+
+import javajs.util.V3;
+
+public interface EigenInterface {
+
+  EigenInterface setM(double[][] n);
+
+  double[] getEigenvalues();
+
+  void fillFloatArrays(V3[] eigenVectors, float[] eigenValues);
+
+  float[][] getEigenvectorsFloatTransposed();
+
+}
diff --git a/src/javajs/api/GenericBinaryDocument.java b/src/javajs/api/GenericBinaryDocument.java
new file mode 100644 (file)
index 0000000..823bd2a
--- /dev/null
@@ -0,0 +1,40 @@
+package javajs.api;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.InputStream;
+import java.util.Map;
+
+
+import javajs.util.SB;
+
+public interface GenericBinaryDocument extends GenericBinaryDocumentReader {
+
+  GenericBinaryDocument setStream(BufferedInputStream bis, boolean isBigEndian);
+
+  void setStreamData(DataInputStream dataInputStream, boolean isBigEndian);
+
+  long getPosition();
+
+  SB getAllDataFiles(String binaryFileList, String firstFile);
+
+  void getAllDataMapped(String replace, String string, Map<String, String> fileData);
+
+  int swapBytesI(int nx);
+
+  short swapBytesS(short s);
+
+  void seek(long i);
+
+  void setOutputChannel(GenericOutputChannel out);
+
+  InputStream getInputStream();
+
+  int readIntLE() throws Exception;
+
+  int readByteArray(byte[] b, int off, int len) throws Exception;
+
+
+  void close();
+
+}
diff --git a/src/javajs/api/GenericBinaryDocumentReader.java b/src/javajs/api/GenericBinaryDocumentReader.java
new file mode 100644 (file)
index 0000000..aea6f63
--- /dev/null
@@ -0,0 +1,25 @@
+package javajs.api;
+
+public interface GenericBinaryDocumentReader {
+
+  byte readByte() throws Exception;
+
+  byte[] readBytes(int n) throws Exception;
+
+  int readUInt8() throws Exception;
+
+  int readInt() throws Exception;
+
+  short readShort() throws Exception;
+
+  int readUnsignedShort() throws Exception;
+
+  long readLong() throws Exception;
+
+  float readFloat() throws Exception;
+
+  double readDouble() throws Exception;
+
+  String readString(int i) throws Exception;
+
+}
diff --git a/src/javajs/api/GenericCifDataParser.java b/src/javajs/api/GenericCifDataParser.java
new file mode 100644 (file)
index 0000000..81a71f7
--- /dev/null
@@ -0,0 +1,45 @@
+package javajs.api;
+
+import java.io.BufferedReader;
+import java.util.Map;
+
+
+public interface GenericCifDataParser {
+
+  static final int NONE = -1;
+
+  String fullTrim(String str);
+
+  Map<String, Object> getAllCifData();
+
+  boolean getData() throws Exception;
+
+  String getColumnName(int i);
+
+  int getColumnCount();
+
+  String getFileHeader();
+
+  Object peekToken() throws Exception;
+
+  Object getTokenPeeked();
+
+  Object getColumnData(int i);
+
+  Object getNextDataToken() throws Exception;
+
+  String getNextToken() throws Exception;
+
+  void parseDataBlockParameters(String[] fields, String key, String data, int[] key2col, int[] col2key) throws Exception;
+
+  String readLine();
+
+  GenericCifDataParser set(GenericLineReader reader, BufferedReader br, boolean debugging);
+
+  String toUnicode(String data);
+
+  String skipLoop(boolean doReport) throws Exception;
+
+  String fixKey(String key);
+
+}
diff --git a/src/javajs/api/GenericColor.java b/src/javajs/api/GenericColor.java
new file mode 100644 (file)
index 0000000..57cf169
--- /dev/null
@@ -0,0 +1,19 @@
+package javajs.api;
+
+/**
+ * GenericColor allows both java.awt.Color and javajs.awt.Color to be
+ * handled by methods that need not distinguish between them. It is used
+ * in the javajs package for the background color of a javajs.swing.JComponent
+ * 
+ * @author hansonr
+ *
+ */
+public interface GenericColor {
+
+       int getRGB();
+
+       int getOpacity255();
+
+       void setOpacity255(int a);
+       
+}
diff --git a/src/javajs/api/GenericImageDialog.java b/src/javajs/api/GenericImageDialog.java
new file mode 100644 (file)
index 0000000..54d8503
--- /dev/null
@@ -0,0 +1,9 @@
+package javajs.api;
+
+public interface GenericImageDialog {
+
+  void closeMe();
+
+  void setImage(Object image);
+
+}
diff --git a/src/javajs/api/GenericImageEncoder.java b/src/javajs/api/GenericImageEncoder.java
new file mode 100644 (file)
index 0000000..9aa7477
--- /dev/null
@@ -0,0 +1,12 @@
+package javajs.api;
+
+import java.util.Map;
+
+import javajs.util.OC;
+
+public interface GenericImageEncoder {
+
+  public boolean createImage(String type, OC out,
+                             Map<String, Object> params) throws Exception;
+
+}
diff --git a/src/javajs/api/GenericLineReader.java b/src/javajs/api/GenericLineReader.java
new file mode 100644 (file)
index 0000000..f113a8b
--- /dev/null
@@ -0,0 +1,5 @@
+package javajs.api;
+
+public interface GenericLineReader {
+  public String readNextLine() throws Exception;
+}
diff --git a/src/javajs/api/GenericOutputChannel.java b/src/javajs/api/GenericOutputChannel.java
new file mode 100644 (file)
index 0000000..73649d7
--- /dev/null
@@ -0,0 +1,22 @@
+package javajs.api;
+
+public interface GenericOutputChannel {
+
+  boolean isBigEndian();
+
+  void writeByteAsInt(int b);
+
+  void write(byte[] b, int off, int n);
+
+  void writeInt(int i);
+
+  void reset();
+
+  String closeChannel();
+
+  void writeLong(long b);
+
+  void writeShort(short i);
+
+
+}
diff --git a/src/javajs/api/GenericZipInputStream.java b/src/javajs/api/GenericZipInputStream.java
new file mode 100644 (file)
index 0000000..e2e8018
--- /dev/null
@@ -0,0 +1,13 @@
+package javajs.api;
+
+
+import java.io.InputStream;
+import java.util.zip.ZipInputStream;
+
+public class GenericZipInputStream extends ZipInputStream implements ZInputStream {
+  
+  public GenericZipInputStream(InputStream in) {
+    super(in);
+  }
+  
+}
\ No newline at end of file
diff --git a/src/javajs/api/GenericZipTools.java b/src/javajs/api/GenericZipTools.java
new file mode 100644 (file)
index 0000000..47a2da2
--- /dev/null
@@ -0,0 +1,47 @@
+package javajs.api;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.Map;
+
+
+public interface GenericZipTools {
+
+  public ZInputStream newZipInputStream(InputStream is);
+  
+  public String getZipDirectoryAsStringAndClose(BufferedInputStream t);
+
+  public InputStream newGZIPInputStream(InputStream bis) throws IOException;
+
+  public InputStream newBZip2InputStream(InputStream bis) throws IOException;
+
+  public Object getZipFileDirectory(BufferedInputStream bis,
+                                          String[] subFileList, int listPtr, boolean asBufferedInputStream);
+
+  public String[] getZipDirectoryAndClose(BufferedInputStream t,
+                                                 String manifestID);
+
+  public void getAllZipData(InputStream bis, String[] subFileList,
+                                String replace, String binaryFileList, String exclude,
+                                Map<String, String> fileData);
+
+  public Object getZipFileContentsAsBytes(BufferedInputStream bis,
+                                                 String[] subFileList, int i);
+
+  public void addZipEntry(Object zos, String fileName) throws IOException;
+
+  public void closeZipEntry(Object zos) throws IOException;
+
+  public Object getZipOutputStream(Object bos);
+
+  public int getCrcValue(byte[] bytes);
+
+  public void readFileAsMap(BufferedInputStream is, Map<String, Object> bdata, String name);
+
+  public String cacheZipContents(BufferedInputStream bis, String shortName,
+                                 Map<String, Object> cache, boolean asByteArray);
+
+  BufferedInputStream getUnGzippedInputStream(byte[] bytes);
+}
diff --git a/src/javajs/api/Interface.java b/src/javajs/api/Interface.java
new file mode 100644 (file)
index 0000000..418d528
--- /dev/null
@@ -0,0 +1,54 @@
+/* $RCSfile$
+ * $Author$
+ * $Date$
+ * $Revision$
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2006  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+package javajs.api;
+
+public class Interface {
+
+  public static Object getInterface(String name) {
+    try {
+      Class<?> x = Class.forName(name);
+      return (x == null ? null : x.newInstance());
+    } catch (Exception e) {
+      System.out.println("Interface.getInterface Error creating instance for " + name + ": \n" + e);
+      return null;
+    }
+  }
+  
+       public static Object getInstanceWithParams(String name, Class<?>[] classes, Object... params) {
+               try {
+                       Class<?> cl = Class.forName(name);
+                       return  cl.getConstructor(classes).newInstance(params);
+               } catch (Exception e) {
+      System.out.println("Interface.getInterfaceWithParams Error creating instance for " + name + ": \n" + e);
+                       return null;
+               }
+       }
+
+
+}
diff --git a/src/javajs/api/JSFunction.java b/src/javajs/api/JSFunction.java
new file mode 100644 (file)
index 0000000..1c6c108
--- /dev/null
@@ -0,0 +1,12 @@
+package javajs.api;
+
+/**
+ * A flag that this object is really a JavaScript function that, for example,
+ * might be called from setTimeout(). 
+ * 
+ * @author Bob Hanson
+ *
+ */
+public interface JSFunction {
+
+}
diff --git a/src/javajs/api/JSInterface.java b/src/javajs/api/JSInterface.java
new file mode 100644 (file)
index 0000000..0f785fa
--- /dev/null
@@ -0,0 +1,30 @@
+package javajs.api;
+
+/** 
+ * called by JSmol JavaScript methods using
+ * 
+ *  this._applet.xxxx()
+ *  
+ */
+public interface JSInterface {
+
+  int cacheFileByName(String fileName, boolean isAdd);
+  void cachePut(String key, Object data);
+  void destroy();
+  String getFullName();
+  void openFileAsyncSpecial(String fileName, int flags);
+  boolean processMouseEvent(int id, int x, int y, int modifiers, long time);
+  void processTwoPointGesture(float[][][] touches);
+  void setDisplay(Object canvas);
+  void setScreenDimension(int width, int height);
+  boolean setStatusDragDropped(int mode, int x, int y, String fileName);
+       void startHoverWatcher(boolean enable);
+       void update();
+
+       // these are not general methods
+//Object getGLmolView();
+//String loadInlineString(String mol, String script, boolean isAppend);
+//String openFile(String fileName);
+
+}
+
diff --git a/src/javajs/api/JSONEncodable.java b/src/javajs/api/JSONEncodable.java
new file mode 100644 (file)
index 0000000..b013279
--- /dev/null
@@ -0,0 +1,7 @@
+package javajs.api;
+
+public interface JSONEncodable {
+
+  String toJSON();
+
+}
diff --git a/src/javajs/api/ResettableStream.java b/src/javajs/api/ResettableStream.java
new file mode 100644 (file)
index 0000000..5ded55f
--- /dev/null
@@ -0,0 +1,7 @@
+package javajs.api;
+
+public interface ResettableStream {
+
+       void resetStream();
+
+}
diff --git a/src/javajs/api/ZInputStream.java b/src/javajs/api/ZInputStream.java
new file mode 100644 (file)
index 0000000..a3310f6
--- /dev/null
@@ -0,0 +1,6 @@
+package javajs.api;
+
+public interface ZInputStream {
+  // placeholder for ZipInputStream not requiring direct access to java.util.zip
+
+}
diff --git a/src/javajs/api/js/J2SObjectInterface.java b/src/javajs/api/js/J2SObjectInterface.java
new file mode 100644 (file)
index 0000000..687f642
--- /dev/null
@@ -0,0 +1,12 @@
+package javajs.api.js;
+
+/**
+ * methods in JSmol JavaScript accessed in Jmol 
+ */
+public interface J2SObjectInterface {
+
+  Object _doAjax(Object url, String postOut, Object bytesOrStringOut, boolean isBinary);
+
+  void _apply(Object func, Object data);
+
+}
diff --git a/src/javajs/api/js/JSInterface.java b/src/javajs/api/js/JSInterface.java
new file mode 100644 (file)
index 0000000..9b26631
--- /dev/null
@@ -0,0 +1,41 @@
+package javajs.api.js;
+
+/**
+ * called by JSmol JavaScript methods using
+ * 
+ * this._applet.xxxx()
+ * 
+ */
+public interface JSInterface {
+
+       int cacheFileByName(String fileName, boolean isAdd);
+
+       void cachePut(String key, Object data);
+
+       void destroy();
+
+       String getFullName();
+
+       void openFileAsyncSpecial(String fileName, int flags);
+
+       boolean processMouseEvent(int id, int x, int y, int modifiers, long time);
+
+       void processTwoPointGesture(float[][][] touches);
+
+       void setDisplay(Object canvas);
+
+       void setScreenDimension(int width, int height);
+
+       boolean setStatusDragDropped(int mode, int x, int y, String fileName);
+
+       void startHoverWatcher(boolean enable);
+       
+       
+
+       // these are not general methods
+       // Object getGLmolView();
+       // String loadInlineString(String mol, String script, boolean isAppend);
+       // String openFile(String fileName);
+
+}
+
diff --git a/src/javajs/export/PDFCreator.java b/src/javajs/export/PDFCreator.java
new file mode 100644 (file)
index 0000000..8306d94
--- /dev/null
@@ -0,0 +1,367 @@
+/* $RCSfile$
+ * $Author: hansonr $
+ * $Date: 2009-06-30 18:58:33 -0500 (Tue, 30 Jun 2009) $
+ * $Revision: 11158 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2002-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.export;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javajs.util.Lst;
+import javajs.util.SB;
+
+
+
+public class PDFCreator {
+  private OutputStream os;
+  private Lst<PDFObject> indirectObjects;
+  private PDFObject root;
+  private PDFObject graphics; 
+//  private PDFObject pageResources;
+//  private PDFObject graphicsResources;
+
+  private int pt;
+  private int xrefPt;
+  private int count;
+
+  private int height;
+  private int width;
+  
+  private Map<String, PDFObject>fonts;
+
+  public PDFCreator() {
+   // for Class.forName  
+  }
+
+  public void setOutputStream(OutputStream os) {
+    this.os = os;
+  }
+
+  public void newDocument(int paperWidth, int paperHeight, boolean isLandscape) {
+    width = (isLandscape ? paperHeight : paperWidth);
+    height = (isLandscape ? paperWidth : paperHeight);
+    System.out.println("Creating PDF with width=" + width + " and height=" + height);
+    fonts = new Hashtable<String, PDFObject>();
+    indirectObjects = new Lst<PDFObject>();
+    //graphicsResources = newObject(null);
+    //pageResources = newObject(null); // will set this to compressed stream later
+    root = newObject("Catalog");
+    PDFObject pages = newObject("Pages");
+    PDFObject page = newObject("Page");
+    PDFObject pageContents = newObject(null);
+    graphics = newObject("XObject");
+    
+    root.addDef("Pages", pages.getRef());
+    pages.addDef("Count", "1");
+    pages.addDef("Kids", "[ " + page.getRef() +" ]");
+    page.addDef("Parent", pages.getRef());
+    page.addDef("MediaBox", "[ 0 0 " + paperWidth + " " + paperHeight + " ]");
+    if (isLandscape)
+      page.addDef("Rotate", "90");
+
+    pageContents.addDef("Length", "?");
+    pageContents.append((isLandscape ? "q 0 1 1 0 0 0 " : "q 1 0 0 -1 0 "+(paperHeight))+" cm /" + graphics.getID() + " Do Q");
+    page.addDef("Contents", pageContents.getRef());   
+    addProcSet(page);
+    addProcSet(graphics);
+    // will add fonts as well as they are needed
+    graphics.addDef("Subtype", "/Form");
+    graphics.addDef("FormType", "1");
+    graphics.addDef("BBox", "[0 0 " + width + " " + height + "]");
+    graphics.addDef("Matrix", "[1 0 0 1 0 0]");
+    graphics.addDef("Length", "?");
+    page.addResource("XObject", graphics.getID(), graphics.getRef());   
+    g("q 1 w 1 J 1 j 10 M []0 d q "); // line width 1, line cap circle, line join circle, miter limit 10, solid
+    clip(0, 0, width, height);
+  }   
+
+  private void addProcSet(PDFObject o) {
+    o.addResource(null, "ProcSet", "[/PDF /Text /ImageB /ImageC /ImageI]");
+  }
+
+  private void clip(int x1, int y1, int x2, int y2) {
+    moveto(x1, y1);
+    lineto(x2, y1);
+    lineto(x2, y2);
+    lineto(x1, y2);
+    g("h W n");
+  }
+
+  public void moveto(int x, int y) {
+    g(x + " " + y  + " m");
+  }
+
+  public void lineto(int x, int y) {
+    g(x + " " + y  + " l");
+  }
+
+  private PDFObject newObject(String type) {
+    PDFObject o = new PDFObject(++count);
+    if (type != null)
+      o.addDef("Type", "/" + type);
+    indirectObjects.addLast(o);
+    return o;
+  }
+
+  public void addInfo(Map<String, String> data) {
+    Hashtable<String, Object> info = new Hashtable<String, Object>();
+    for (Entry<String, String> e: data.entrySet()) {
+      String value = "(" + e.getValue().replace(')','_').replace('(','_')+ ")";
+      info.put(e.getKey(), value);      
+    }
+    root.addDef("Info", info);
+  }
+
+  private PDFObject addFontResource(String fname) {
+    PDFObject f = newObject("Font");
+    fonts.put(fname, f);
+    f.addDef("BaseFont", fname);
+    f.addDef("Encoding", "/WinAnsiEncoding");
+    f.addDef("Subtype", "/Type1");
+    graphics.addResource("Font", f.getID(), f.getRef());
+    return f;
+  }
+
+  private Map<Object, PDFObject> images;
+  
+  public void addImageResource(Object newImage, int width, int height, int[] buffer, boolean isRGB) {
+    PDFObject imageObj = newObject("XObject");
+    if (images == null)
+      images = new Hashtable<Object, PDFObject>();
+    images.put(newImage, imageObj);   
+    imageObj.addDef("Subtype", "/Image");
+    imageObj.addDef("Length", "?");
+    imageObj.addDef("ColorSpace", isRGB ? "/DeviceRGB" : "/DeviceGray");
+    imageObj.addDef("BitsPerComponent", "8");
+    imageObj.addDef("Width", "" + width);
+    imageObj.addDef("Height", "" + height);
+    graphics.addResource("XObject", imageObj.getID(), imageObj.getRef());
+    int n = buffer.length;
+    byte[] stream = new byte[n * (isRGB ? 3 : 1)];
+    if (isRGB) {
+      for (int i = 0, pt = 0; i < n; i++) {
+        stream[pt++] = (byte) ((buffer[i] >> 16) & 0xFF);
+        stream[pt++] = (byte) ((buffer[i] >> 8) & 0xFF);
+        stream[pt++] = (byte) (buffer[i] & 0xFF);
+      }
+    } else {
+      for (int i = 0; i < n; i++)
+        stream[i] = (byte) buffer[i];
+    }
+    imageObj.setStream(stream);
+    graphics.addResource("XObject", imageObj.getID(), imageObj.getRef());
+  }
+
+  public void g(String cmd) {
+    graphics.append(cmd).appendC('\n');
+  }
+
+  private void output(String s) throws IOException {
+   byte[] b = s.getBytes();
+   os.write(b, 0, b.length);
+   pt += b.length;
+  }
+
+  public void closeDocument() throws IOException {
+    g("Q Q");
+    outputHeader();
+    writeObjects();
+    writeXRefTable();
+    writeTrailer();
+    os.flush();
+    os.close();
+  }
+
+  private void outputHeader() throws IOException {
+    output("%PDF-1.3\n%");
+    byte[] b = new byte[] {-1, -1, -1, -1};
+    os.write(b, 0, b.length);
+    pt += 4;
+    output("\n");
+  }
+
+  private void writeTrailer() throws IOException {
+    PDFObject trailer = new PDFObject(-2);
+    output("trailer");
+    trailer.addDef("Size", "" + indirectObjects.size());
+    trailer.addDef("Root", root.getRef());
+    trailer.output(os);
+    output("startxref\n");
+    output("" + xrefPt + "\n");
+    output("%%EOF\n");
+  }
+
+  /**
+   * Write Font objects first.
+   * 
+   * @throws IOException
+   */
+  private void writeObjects() throws IOException {
+    int nObj = indirectObjects.size();
+    for (int i = 0; i < nObj; i++) {
+      PDFObject o = indirectObjects.get(i);
+      if (!o.isFont())
+        continue;
+      o.pt = pt;
+      pt += o.output(os);
+    }
+    for (int i = 0; i < nObj; i++) {
+      PDFObject o = indirectObjects.get(i);
+      if (o.isFont())
+        continue;
+      o.pt = pt;
+      pt += o.output(os);
+    }
+  }
+
+  private void writeXRefTable() throws IOException {
+    xrefPt = pt;
+    int nObj = indirectObjects.size();
+    SB sb = new SB();
+    // note trailing space, needed because \n is just one character
+    sb.append("xref\n0 " + (nObj + 1) 
+        + "\n0000000000 65535 f\r\n");
+    for (int i = 0; i < nObj; i++) {
+      PDFObject o = indirectObjects.get(i);
+      String s = "0000000000" + o.pt;
+      sb.append(s.substring(s.length() - 10));
+      sb.append(" 00000 n\r\n");
+    }
+    output(sb.toString());
+  }
+
+  public boolean canDoLineTo() {
+    return true;
+  }
+
+  public void fill() {
+    g("f");   
+  }
+
+  public void stroke() {
+    g("S");   
+  }
+
+  public void doCircle(int x, int y, int r, boolean doFill) {
+    double d = r*4*(Math.sqrt(2)-1)/3;
+    double dx = x;
+    double dy = y;
+    g((dx + r) + " " + dy + " m");
+    g((dx + r) + " " + (dy + d) + " " + (dx + d) + " " + (dy + r) + " " + (dx) + " " + (dy + r) + " "  + " c");
+    g((dx - d) + " " + (dy + r) + " " + (dx - r) + " " + (dy + d) + " " + (dx - r) + " " + (dy) + " c");
+    g((dx - r) + " " + (dy - d) + " " + (dx - d) + " " + (dy - r) + " " + (dx) + " " + (dy - r) + " c");
+    g((dx + d) + " " + (dy - r) + " " + (dx + r) + " " + (dy - d) + " " + (dx + r) + " " + (dy) + " c");
+    g(doFill ? "f" : "s");
+  }
+
+  public void doPolygon(int[] axPoints, int[] ayPoints, int nPoints, boolean doFill) {
+    moveto(axPoints[0], ayPoints[0]);
+    for (int i = 1; i < nPoints; i++)
+      lineto(axPoints[i], ayPoints[i]);
+    g(doFill ? "f" : "s");
+  }
+
+  public void doRect(int x, int y, int width, int height, boolean doFill) {
+    g(x + " " + y + " " + width + " " + height + " re " + (doFill ? "f" : "s"));
+  }
+
+  public void drawImage(Object image, int destX0, int destY0,
+      int destX1, int destY1, int srcX0, int srcY0, int srcX1, int srcY1) {
+    PDFObject imageObj = images.get(image);
+    if (imageObj == null)
+      return;
+    g("q");
+    clip(destX0, destY0, destX1, destY1);
+    double iw = Double.parseDouble((String) imageObj.getDef("Width"));
+    double ih = Double.parseDouble((String) imageObj.getDef("Height"));   
+    double dw = (destX1 - destX0 + 1);
+    double dh  = (destY1 - destY0 + 1);
+    double sw = (srcX1 - srcX0 + 1);
+    double sh = (srcY1 - srcY0 + 1);
+    double scaleX = dw / sw;
+    double scaleY = dh / sh;
+    double transX = destX0 - srcX0 * scaleX;
+    double transY = destY0 + (ih - srcY0) * scaleY;
+    g(scaleX*iw + " 0 0 " + -scaleY*ih + " " + transX + " " + transY + " cm");
+    g("/" + imageObj.getID() + " Do");
+    g("Q");
+  }
+
+  public void drawStringRotated(String s, int x, int y, int angle) {
+    g("q " + getRotation(angle) + " " + x + " " + y
+        + " cm BT(" + s + ")Tj ET Q");
+  }
+
+  public String getRotation(int angle) {    
+    float cos = 0, sin = 0;
+    switch (angle) {
+    case 0:
+      cos = 1;
+      break;
+    case 90:
+      sin = 1;
+      break;
+    case -90:
+      sin = -1;
+      break;
+    case 180:
+      cos = -1;
+      break;
+    default:
+      float a = (float) (angle * (Math.PI / 180));
+      cos = (float) Math.cos(a);
+      sin = (float) Math.sin(a);
+      if (Math.abs(cos) < 0.0001)
+        cos = 0;
+      if (Math.abs(sin) < 0.0001)
+        sin = 0;
+    }
+    return  cos + " " + sin + " " + sin + " " + -cos;
+  }
+
+  public void setColor(float[] rgb, boolean isFill) {
+    g(rgb[0] + " " + rgb[1] + " " + rgb[2] + (isFill ? " rg" : " RG"));
+  }
+
+  public void setFont(String fname, float size) {
+    PDFObject f = fonts.get(fname);
+    if (f == null)
+      f = addFontResource(fname);
+    g("/" + f.getID() + " " + size + " Tf");
+  }
+
+  public void setLineWidth(float width) {
+    g(width + " w");    
+  }
+
+  public void translateScale(float x, float y, float scale) {
+    g(scale + " 0 0 " + scale + " " + x + " " + y + " cm");
+  }
+
+}
diff --git a/src/javajs/export/PDFObject.java b/src/javajs/export/PDFObject.java
new file mode 100644 (file)
index 0000000..176dc19
--- /dev/null
@@ -0,0 +1,152 @@
+package javajs.export;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import javajs.util.SB;
+
+
+/**
+ * A rudimentary class for working with PDF document creation.
+ * Written from scratch based on PDF Reference 13.
+ * 
+ * @author hansonr  Bob Hanson hansonr@stolaf.edu  10/28/2013
+ * 
+ */
+class PDFObject extends SB {   
+       private Map<String, Object> dictionary;
+       private byte[] stream;
+       private int index;
+       String type;
+       int len;
+       int pt;
+       
+       PDFObject(int index) {
+               this.index = index;
+       }
+
+       String getRef() {
+               return index + " 0 R";
+       }
+       
+       String getID() {
+               return type.substring(0, 1) + index;
+       }
+       
+       boolean isFont() {
+               return "Font".equals(type);
+       }
+
+       void setStream(byte[] stream) {
+               this.stream = stream;
+       }
+
+       Object getDef(String key) {
+               return dictionary.get(key);
+       }
+       
+       void addDef(String key, Object value) {
+               if (dictionary  == null)
+                       dictionary = new Hashtable<String, Object>();
+               dictionary.put(key, value);
+               if (key.equals("Type"))
+                       type = ((String) value).substring(1);
+       }
+       
+       void setAsStream() {
+               stream = toBytes(0, -1);
+               setLength(0);
+       }
+       
+       int output(OutputStream os) throws IOException {
+               if (index > 0) {
+                       String s = index + " 0 obj\n";
+                       write(os, s.getBytes(), 0);
+               }
+               int streamLen = 0;
+               if (dictionary != null) {
+                       if (dictionary.containsKey("Length")) {
+                               if (stream == null)
+                                       setAsStream();
+                               streamLen = stream.length;
+                               boolean doDeflate = (streamLen > 1000);
+        if (doDeflate) {
+          Deflater deflater = new Deflater(9);
+          ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024);
+          DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes,
+              deflater);
+          compBytes.write(stream, 0, streamLen);
+          compBytes.finish();
+          stream = outBytes.toByteArray();
+          dictionary.put("Filter", "/FlateDecode");
+          streamLen = stream.length;
+        }
+                               dictionary.put("Length", "" + streamLen);
+                       }
+                       write(os, getDictionaryText(dictionary, "\n").getBytes(), 0);
+               }
+               if (length() > 0)
+                       write(os, this.toString().getBytes(), 0);
+               if (stream != null) {
+                       write(os, "stream\r\n".getBytes(), 0);
+                       write(os, stream, streamLen);
+                       write(os, "\r\nendstream\r\n".getBytes(), 0);
+               }
+               if (index > 0)
+                       write(os, "endobj\n".getBytes(), 0);
+               return len;
+       }
+
+       private void write(OutputStream os, byte[] bytes, int nBytes) throws IOException {
+               if (nBytes == 0)
+                       nBytes = bytes.length;
+               len += nBytes;
+               os.write(bytes, 0, nBytes);
+       }
+
+       @SuppressWarnings("unchecked")
+       private String getDictionaryText(Map<String, Object> d, String nl) {
+               SB sb = new SB();
+               sb.append("<<");
+               if (d.containsKey("Type"))
+                       sb.append("/Type").appendO(d.get("Type"));
+               for (Entry<String, Object> e : d.entrySet()) {
+                       String s = e.getKey();
+                       if (s.equals("Type") || s.startsWith("!"))
+                               continue;
+                       sb.append("/" + s);
+                       Object o = e.getValue();
+                       if (o instanceof Map<?, ?>) {
+                               sb.append((getDictionaryText((Map<String, Object>) o, "")));
+                               continue;
+                       }
+                       s = (String) e.getValue();
+                       if (!s.startsWith("/"))
+                               sb.append(" ");
+                       sb.appendO(s);
+               }
+               return (sb.length() > 3 ? sb.append(">>").append(nl).toString() : "");
+       }
+
+       @SuppressWarnings("unchecked")
+       private Map<String, Object> createSubdict(Map<String, Object> d0, String dict) {
+               Map<String, Object> d = (Map<String, Object>) d0.get(dict);
+               if (d == null)
+                       d0.put(dict, d = new Hashtable<String, Object>());
+               return d;
+       }
+
+       void addResource(String type, String key, String value) {
+               Map<String, Object> r = createSubdict(dictionary, "Resources");
+               if (type != null)
+                       r = createSubdict(r, type);
+               r.put(key, value);
+       }
+       
+}
diff --git a/src/javajs/img/BMPDecoder.java b/src/javajs/img/BMPDecoder.java
new file mode 100644 (file)
index 0000000..4c94c9b
--- /dev/null
@@ -0,0 +1,211 @@
+/* $RCSfile$
+ * $Author: nicove $
+ * $Date: 2007-03-30 12:26:16 -0500 (Fri, 30 Mar 2007) $
+ * $Revision: 7275 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2002-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.img;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+
+import javajs.util.Rdr;
+
+/**
+ * src: http://www.javaworld.com/article/2077542/learn-java/java-tip-43--how-to-
+ * read-8--and-24-bit-microsoft-windows-bitmaps-in-java-applications.html
+ *
+ * see also: http://en.wikipedia.org/wiki/BMP_file_format
+ * 
+ * Modified by Bob Hanson hansonr@stolaf.edu
+ * 
+ * @author Bob Hanson (hansonr@stolaf.edu)
+ * 
+ */
+public class BMPDecoder {
+
+  public BMPDecoder() {
+    // for reflection
+  }
+  
+  private BufferedInputStream bis;
+
+  /**
+   * original comment:
+   * 
+   * loadbitmap() method converted from Windows C code. Reads only uncompressed
+   * 24- and 8-bit images. Tested with images saved using Microsoft Paint in
+   * Windows 95. If the image is not a 24- or 8-bit image, the program refuses
+   * to even try. I guess one could include 4-bit images by masking the byte by
+   * first 1100 and then 0011. I am not really interested in such images. If a
+   * compressed image is attempted, the routine will probably fail by generating
+   * an IOException. Look for variable ncompression to be different from 0 to
+   * indicate compression is present.
+   * 
+   * @param bytes
+   * @return [image byte array, width, height]
+   */
+  public Object[] decodeWindowsBMP(byte[] bytes) {
+    try {
+      bis = Rdr.getBIS(bytes);
+      temp = new byte[4];
+      // read BITMAPFILEHEADER
+      if (readByte() != 'B' || readByte() != 'M')
+        return null;
+      readInt(); // file size; ignored
+      readShort(); // reserved
+      readShort(); // reserved
+      readInt(); // ptr to pixel array; ignored
+      int imageWidth, imageHeight, bitsPerPixel, nColors = 0, imageSize = 0;
+      // read BITMAP header
+      int headerSize = readInt();
+      switch (headerSize) {
+      case 12:
+        // BITMAPCOREHEADER
+        imageWidth = readShort();
+        imageHeight = readShort();
+        readShort(); // planes
+        bitsPerPixel = readShort();
+        break;
+      case 40:
+        // BITMAPINFOHEADER
+        imageWidth = readInt();
+        imageHeight = readInt();
+        readShort(); // planes
+        bitsPerPixel = readShort();
+        int ncompression = readInt();
+        if (ncompression != 0) {
+          System.out.println("BMP Compression is :" + ncompression
+              + " -- aborting");
+          return null;
+        }
+        imageSize = readInt();
+        readInt(); // hres
+        readInt(); // vres
+        nColors = readInt();
+        readInt(); // colors used
+        break;
+      default:
+        System.out.println("BMP Header unrecognized, length=" + headerSize
+            + " -- aborting");
+        return null;
+      }
+      boolean isYReversed = (imageHeight < 0);
+      if (isYReversed)
+        imageHeight = -imageHeight;
+      int nPixels = imageHeight * imageWidth;
+      int bytesPerPixel = bitsPerPixel / 8;
+      nColors = (nColors > 0 ? nColors : 1 << bitsPerPixel);
+      int npad = (bytesPerPixel == 4 ? 0
+          : imageSize == 0 ? 4 - (imageWidth % 4) : (imageSize / imageHeight)
+              - imageWidth * bytesPerPixel) % 4;
+      int[] palette;
+      int[] buf = new int[nPixels];
+      int dpt = (isYReversed ? imageWidth : -imageWidth);
+      int pt0 = (isYReversed ? 0 : nPixels + dpt);
+      int pt1 = (isYReversed ? nPixels : dpt);
+      switch (bitsPerPixel) {
+      case 32:
+      case 24:
+        for (int pt = pt0; pt != pt1; pt += dpt, pad(npad))
+          for (int i = 0; i < imageWidth; i++)
+            buf[pt + i] = readColor(bytesPerPixel);
+        break;
+      case 8:
+        palette = new int[nColors];
+        for (int i = 0; i < nColors; i++)
+          palette[i] = readColor(4);
+        for (int pt = pt0; pt != pt1; pt += dpt, pad(npad))
+          for (int i = 0; i < imageWidth; i++)
+            buf[pt + i] = palette[readByte()];
+        break;
+      case 4:
+        npad = (4 - (((imageWidth + 1) / 2) % 4)) % 4;
+        palette = new int[nColors];
+        for (int i = 0; i < nColors; i++)
+          palette[i] = readColor(4);
+        int b4 = 0;
+        for (int pt = pt0; pt != pt1; pt += dpt, pad(npad))
+          for (int i = 0, shift = 4; i < imageWidth; i++, shift = 4 - shift)
+            buf[pt + i] = palette[((shift == 4 ? (b4 = readByte()) : b4) >> shift) & 0xF];
+        break;
+      case 1:
+        int color1 = readColor(3);
+        int color2 = readColor(3);
+        npad = (4 - (((imageWidth + 7) / 8) % 4)) % 4;
+        int b = 0;
+        for (int pt = pt0; pt != pt1; pt += dpt, pad(npad))
+          for (int i = 0, bpt = -1; i < imageWidth; i++, bpt--) {
+            if (bpt < 0) {
+              b = readByte();
+              bpt = 7;
+            }
+            buf[pt + i] = ((b & (1 << bpt)) == 0 ? color1 : color2);
+          }
+        break;
+      case 64:
+      case 2:
+      default:
+        System.out
+            .println("Not a 32-, 24-, 8-, 4-, or 1-bit Windows Bitmap, aborting...");
+        return null;
+      }
+      return new Object[] { buf, Integer.valueOf(imageWidth),
+          Integer.valueOf(imageHeight) };
+    } catch (Exception e) {
+      System.out.println("Caught exception in loadbitmap!");
+    }
+    return null;
+  }
+
+  private boolean pad(int npad) throws IOException {
+    for (int i = 0; i < npad; i++)
+      readByte();
+    return true;
+  }
+
+  private byte[] temp;
+  
+  private int readColor(int n) throws IOException {
+    bis.read(temp, 0, n);
+    return 0xff << 24 | ((temp[2] & 0xff) << 16)
+        | ((temp[1] & 0xff) << 8) | temp[0] & 0xff;
+  }
+
+  private int readInt() throws IOException {
+    bis.read(temp, 0, 4);
+    return ((temp[3] & 0xff) << 24) | ((temp[2] & 0xff) << 16)
+        | ((temp[1] & 0xff) << 8) | temp[0] & 0xff;
+  }
+
+  private int readShort() throws IOException {
+    bis.read(temp, 0, 2);
+    return ((temp[1] & 0xff) << 8) | temp[0] & 0xff;
+  }
+
+  private int readByte() throws IOException {
+    bis.read(temp, 0, 1);
+    return temp[0] & 0xff;
+  }
+
+}
diff --git a/src/javajs/img/CRCEncoder.java b/src/javajs/img/CRCEncoder.java
new file mode 100644 (file)
index 0000000..77ed11e
--- /dev/null
@@ -0,0 +1,110 @@
+package javajs.img;
+
+import java.util.zip.CRC32;
+
+
+import javajs.util.AU;
+
+abstract class CRCEncoder extends ImageEncoder {
+
+  protected int startPos, bytePos;
+  
+  private CRC32 crc;  
+  protected byte[] pngBytes;  
+  protected int dataLen;
+  private byte[] int2 = new byte[2];
+  private byte[] int4 = new byte[4];
+
+  CRCEncoder() {
+    pngBytes = new byte[250];
+    crc = new CRC32();
+  }
+
+  protected void setData(byte[] b, int pt) {
+    pngBytes = b;
+    dataLen = b.length;
+    startPos = bytePos = pt;
+  }
+
+  protected byte[] getBytes() {
+    return (dataLen == pngBytes.length ? pngBytes : AU.arrayCopyByte(
+        pngBytes, dataLen));
+  }
+
+  protected void writeCRC() {
+    crc.reset();
+    crc.update(pngBytes, startPos, bytePos - startPos);
+    writeInt4((int) crc.getValue());
+  }
+
+  /**
+   * Write a two-byte integer into the pngBytes array at a given position.
+   *
+   * @param n The integer to be written into pngBytes.
+   */
+  protected void writeInt2(int n) {
+    int2[0] = (byte) ((n >> 8) & 0xff);
+    int2[1] = (byte) (n & 0xff);
+    writeBytes(int2);
+  }
+
+  /**
+   * Write a four-byte integer into the pngBytes array at a given position.
+   *
+   * @param n The integer to be written into pngBytes.
+   */
+  protected void writeInt4(int n) {
+    getInt4(n, int4);
+    writeBytes(int4);
+  }
+
+  protected static void getInt4(int n, byte[] int4) {
+    int4[0] = (byte) ((n >> 24) & 0xff);
+    int4[1] = (byte) ((n >> 16) & 0xff);
+    int4[2] = (byte) ((n >> 8) & 0xff);
+    int4[3] = (byte) (n & 0xff);
+  }
+
+  /**
+   * Write a single byte into the pngBytes array at a given position.
+   *
+   * @param b The byte to be written into pngBytes.
+   */
+  protected void writeByte(int b) {
+    byte[] temp = {
+      (byte) b
+    };
+    writeBytes(temp);
+  }
+
+  /**
+   * Write a string into the pngBytes array at a given position.
+   * This uses the getBytes method, so the encoding used will
+   * be its default.
+   *
+   * @param s The string to be written into pngBytes.
+   * @see java.lang.String#getBytes()
+   */
+  protected void writeString(String s) {
+    writeBytes(s.getBytes());
+  }
+
+  /**
+   * Write an array of bytes into the pngBytes array. 
+   * Both dataLen and bytePos are updated. If we don't have 
+   * enough room, this is certainly in image data writing,
+   * so we add just enough for CRC END CRC
+   * 
+   * @param data
+   *        The data to be written into pngBytes.
+   */
+  protected void writeBytes(byte[] data) {
+    int newPos = bytePos + data.length;
+    dataLen = Math.max(dataLen, newPos);
+    if (newPos > pngBytes.length)
+      pngBytes = AU.arrayCopyByte(pngBytes, newPos + 16);
+    System.arraycopy(data, 0, pngBytes, bytePos, data.length);
+    bytePos = newPos;
+  }
+
+}
diff --git a/src/javajs/img/GifEncoder.java b/src/javajs/img/GifEncoder.java
new file mode 100644 (file)
index 0000000..287f9c3
--- /dev/null
@@ -0,0 +1,1142 @@
+/* $RCSfile$
+ * $Author: hansonr $
+ * $Date: 2007-06-02 12:14:13 -0500 (Sat, 02 Jun 2007) $
+ * $Revision: 7831 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2000-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+//  Final encoding code from http://acme.com/resources/classes/Acme/JPM/Encoders/GifEncoder.java
+//
+//  GifEncoder - write out an image as a GIF
+// 
+// 
+//  Transparency handling and variable bit size courtesy of Jack Palevich.
+//  
+//  Copyright (C)1996,1998 by Jef Poskanzer <jef@mail.acme.com>. All rights reserved.
+//  
+//  Redistribution and use in source and binary forms, with or without
+//  modification, are permitted provided that the following conditions
+//  are met:
+//  1. Redistributions of source code must retain the above copyright
+//     notice, this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright
+//     notice, this list of conditions and the following disclaimer in the
+//     documentation and/or other materials provided with the distribution.
+// 
+//  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+//  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+//  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+//  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+//  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+//  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+//  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+//  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+//  SUCH DAMAGE.
+// 
+//  Visit the ACME Labs Java page for up-to-date versions of this and other
+//  fine Java utilities: http://www.acme.com/java/
+// 
+/// Write out an image as a GIF.
+// <P>
+// <A HREF="/resources/classes/Acme/JPM/Encoders/GifEncoder.java">Fetch the software.</A><BR>
+// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
+// <P>
+// @see ToGif
+
+package javajs.img;
+
+import javajs.util.CU;
+import javajs.util.Lst;
+import javajs.util.M3;
+import javajs.util.P3;
+
+import java.util.Hashtable;
+import java.util.Map;
+import java.io.IOException;
+
+/**
+ * 
+ * GifEncoder extensively adapted for Jmol by Bob Hanson
+ * 
+ * Color quantization roughly follows the GIMP method
+ * "dither Floyd-Steinberg (normal)" but with some twists. (For example, we
+ * exclude the background color.)
+ * 
+ * Note that although GIMP code annotation refers to "median-cut", it is really
+ * using MEAN-cut. That is what I use here as well.
+ * 
+ * -- commented code allows visualization of the color space using Jmol. Very
+ * enlightening!
+ * 
+ * -- much simplified interface with ImageEncoder
+ * 
+ * -- uses simple Hashtable with Integer() to catalog colors
+ * 
+ * -- allows progressive production of animated GIF via Jmol CAPTURE command
+ * 
+ * -- uses general purpose javajs.util.OutputChannel for byte-handling options
+ * such as posting to a server, writing to disk, and retrieving bytes.
+ * 
+ * -- allows JavaScript port
+ * 
+ * -- Bob Hanson, first try: 24 Sep 2013; final coding: 9 Nov 2014
+ * 
+ * 
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+
+public class GifEncoder extends ImageEncoder {
+
+  private Map<String, Object> params;
+  private P3[] palette;
+  private int backgroundColor;
+
+  private boolean interlaced;
+  private boolean addHeader = true;
+  private boolean addImage = true;
+  private boolean addTrailer = true;
+  private boolean isTransparent;
+  private boolean floydSteinberg = true;
+  private boolean capturing;
+  private boolean looping;
+
+  private int delayTime100ths = -1;
+  private int bitsPerPixel = 1;
+
+  private int byteCount;
+
+  /**
+   * we allow for animated GIF by being able to re-enter the code with different
+   * parameters held in params
+   * 
+   * 
+   */
+  @Override
+  protected void setParams(Map<String, Object> params) {
+    this.params = params;
+    Integer ic = (Integer) params.get("transparentColor");
+    if (ic == null) {
+      ic = (Integer) params.get("backgroundColor");
+      if (ic != null)
+        backgroundColor = ic.intValue();
+    } else {
+      backgroundColor = ic.intValue();
+      isTransparent = true;
+    }
+
+    interlaced = (Boolean.TRUE == params.get("interlaced"));
+    if (params.containsKey("captureRootExt") // file0000.gif 
+        || !params.containsKey("captureMode")) // animated gif
+      return;
+    interlaced = false;
+    capturing = true;
+    try {
+      byteCount = ((Integer) params.get("captureByteCount")).intValue();
+    } catch (Exception e) {
+      // ignore
+    }
+    switch ("maec"
+        .indexOf(((String) params.get("captureMode")).substring(0, 1))) {
+    case 0: //"movie"
+      params.put("captureMode", "add");
+      addImage = false;
+      addTrailer = false;
+      break;
+    case 1: // add 
+      addHeader = false;
+      addTrailer = false;
+      int fps = Math.abs(((Integer) params.get("captureFps")).intValue());
+      delayTime100ths = (fps == 0 ? 0 : 100 / fps);
+      looping = (Boolean.FALSE != params.get("captureLooping"));
+      break;
+    case 2: // end
+      addHeader = false;
+      addImage = false;
+      break;
+    case 3: // cancel
+      addHeader = false;
+      addImage = false;
+      out.cancel();
+      break;
+    }
+  }
+
+  @Override
+  protected void generate() throws IOException {
+    if (addHeader)
+      writeHeader();
+    addHeader = false; // only one header
+    if (addImage) {
+      createPalette();
+      writeGraphicControlExtension();
+      if (delayTime100ths >= 0 && looping)
+        writeNetscapeLoopExtension();
+      writeImage();
+    }
+  }
+
+  @Override
+  protected void close() {
+    if (addTrailer) {
+      writeTrailer();
+    } else {
+      doClose = false;
+    }
+    if (capturing)
+      params.put("captureByteCount", Integer.valueOf(byteCount));
+  }
+
+  //////////////  256-color quantization  //////////////
+
+  /**
+   * a color point in normalized L*a*b space with a flag indicating whether it
+   * is the background color
+   */
+  private class ColorItem extends P3 {
+    /**
+        * 
+        */
+       protected boolean isBackground;
+
+    ColorItem(int rgb, boolean isBackground) {
+      this.isBackground = isBackground;
+      setT(toLABnorm(rgb));
+    }
+  }
+
+  /**
+   * A list of normalized L*a*b points with an index and a center and volume
+   * 
+   */
+  private class ColorCell extends Lst<P3> {
+
+    /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+       protected int index;
+    protected P3 center;
+
+    private float volume;
+
+    ColorCell(int index) {
+      this.index = index;
+    }
+
+    /**
+     * @param doVisualize
+     *        debugging only
+     * @return volume in normalized L*a*b space
+     */
+    public float getVolume(boolean doVisualize) {
+      if (volume != 0)
+        return volume;
+      if (size() < 2)
+        return -1;
+      //if (true)
+      //return lst.size();
+      //float d;
+      float maxx = -Integer.MAX_VALUE;
+      float minx = Integer.MAX_VALUE;
+      float maxy = -Integer.MAX_VALUE;
+      float miny = Integer.MAX_VALUE;
+      float maxz = -Integer.MAX_VALUE;
+      float minz = Integer.MAX_VALUE;
+      int n = size();
+      for (int i = n; --i >= 0;) {
+        P3 xyz = get(i);
+        if (xyz.x < minx)
+          minx = xyz.x;
+        if (xyz.y < miny)
+          miny = xyz.y;
+        if (xyz.z < minz)
+          minz = xyz.z;
+        if (xyz.x > maxx)
+          maxx = xyz.x;
+        if (xyz.y > maxy)
+          maxy = xyz.y;
+        if (xyz.z > maxz)
+          maxz = xyz.z;
+      }
+      float dx = (maxx - minx);
+      float dy = (maxy - miny);
+      float dz = (maxz - minz);
+      // Jmol visualization only
+      //      if (doVisualize) {
+      //        P3 ptRGB = toRGB(center);
+      //        drawPt(index, -size(), ptRGB);
+      //        //for (int i = n; --i >= 0;)
+      //        //drawPt(index, i, toRGB(get(i)));
+      //        P3 pt0 = toRGB(P3.new3(Math.max(minx, 0), Math.max(miny, 0),
+      //            Math.max(minz, 0)));
+      //        P3 pt1 = toRGB(P3.new3(Math.min(maxx, 100), Math.min(maxy, 100),
+      //            Math.min(maxz, 100)));
+      //        rgbToXyz(pt0, pt0);
+      //        xyzToLab(pt0, pt0);
+      //        rgbToXyz(pt1, pt1);
+      //        xyzToLab(pt1, pt1);
+      //        System.out.println("boundbox corners " + pt0 + " " + pt1);
+      //        System.out.println("draw d" + index + " boundbox color " + ptRGB
+      //            + " mesh nofill");
+      //      }
+      return volume = dx * dx + dy * dy + dz * dz;
+    }
+
+    //    // Jmol visualization only
+    //      private void drawPt(int index, int i, P3 rgb) {
+    //        boolean isMain = (i < 0);
+    //      P3 lab = rgbToXyz(rgb, null);
+    //      xyzToLab(lab, lab);
+    //      System.out.println("draw d" + index + (isMain ? "_" : "_" + i) + " width "
+    //          + (isMain ? 1.0 : 0.2) + " " + lab
+    //          + " color " + rgb + (isMain ? " '" + -i + "'" : ""));
+    //      }
+
+    /**
+     * Set the average normalized L*a*b value for this cell and return its RGB point
+     * 
+     * @return RGB point
+     * 
+     */
+    protected P3 setColor() {
+      int count = size();
+      center = new P3();
+      for (int i = count; --i >= 0;)
+        center.add(get(i));
+      center.scale(1f / count);
+      // Jmol visualization only
+      //volume = 0;
+      //getVolume(true); 
+      return toRGB(center);
+    }
+
+    /**
+     * use median_cut algorithm to split the cell, creating a doubly linked
+     * list.
+     * 
+     * Paul Heckbert, MIT thesis COLOR IMAGE QUANTIZATION FOR FRAME BUFFER
+     * DISPLAY https://www.cs.cmu.edu/~ph/ciq_thesis
+     * 
+     * except, as in GIMP, we use center (not median) here.
+     * 
+     * @param cells
+     * @return true if split
+     */
+    protected boolean splitCell(Lst<ColorCell> cells) {
+      int n = size();
+      if (n < 2)
+        return false;
+      int newIndex = cells.size();
+      ColorCell newCell = new ColorCell(newIndex);
+      cells.addLast(newCell);
+      float[][] ranges = new float[3][3];
+      for (int ic = 0; ic < 3; ic++) {
+        float low = Float.MAX_VALUE;
+        float high = -Float.MAX_VALUE;
+        for (int i = n; --i >= 0;) {
+          P3 lab = get(i);
+          float v = (ic == 0 ? lab.x : ic == 1 ? lab.y : lab.z);
+          if (low > v)
+            low = v;
+          if (high < v)
+            high = v;
+        }
+        ranges[0][ic] = low;
+        ranges[1][ic] = high;
+        ranges[2][ic] = high - low;
+      }
+      float[] r = ranges[2];
+      int mode = (r[0] >= r[1] ? (r[0] >= r[2] ? 0 : 2) : r[1] >= r[2] ? 1 : 2);
+      float val = ranges[0][mode] + ranges[2][mode] / 2;
+      volume = 0; // recalculate volume if needed
+      switch (mode) {
+      case 0:
+        for (int i = n; --i >= 0;)
+          if (get(i).x >= val)
+            newCell.addLast(removeItemAt(i));
+        break;
+      case 1:
+        for (int i = n; --i >= 0;)
+          if (get(i).y >= val)
+            newCell.addLast(removeItemAt(i));
+        break;
+      case 2:
+        for (int i = size(); --i >= 0;)
+          if (get(i).z >= val)
+            newCell.addLast(removeItemAt(i));
+        break;
+      }
+      return true;
+    }
+  }
+
+  /**
+   * Quantize all colors and create the final palette;
+   * replace pixels[] with an array of color indices.
+   * 
+   */
+  private void createPalette() {
+
+    // catalog all pixel colors
+
+    Lst<ColorItem> tempColors = new Lst<ColorItem>();
+    Map<Integer, ColorItem> ciHash = new Hashtable<Integer, ColorItem>();
+    for (int i = 0, n = pixels.length; i < n; i++) {
+      int rgb = pixels[i];
+      Integer key = Integer.valueOf(rgb);
+      ColorItem item = ciHash.get(key);
+      if (item == null) {
+        item = new ColorItem(rgb, rgb == backgroundColor);
+        ciHash.put(key, item);
+        tempColors.addLast(item);
+      }
+    }
+    int nColors = tempColors.size();
+    System.out.println("GIF total image colors: " + nColors);
+    ciHash = null;
+
+    // create a set of <= 256 color cells
+
+    Lst<ColorCell> cells = quantizeColors(tempColors);
+    nColors = cells.size();
+    System.out.println("GIF final color count: " + nColors);
+
+    // generate the palette and map each cell's rgb color to itself
+
+    Map<Integer, ColorCell> colorMap = new Hashtable<Integer, ColorCell>();
+    bitsPerPixel = (nColors <= 2 ? 1 : nColors <= 4 ? 2 : nColors <= 16 ? 4 : 8);
+    palette = new P3[1 << bitsPerPixel];
+    for (int i = 0; i < nColors; i++) {
+      ColorCell c = cells.get(i);
+      colorMap.put(
+          Integer.valueOf(CU.colorPtToFFRGB(palette[i] = c.setColor())), c);
+    }
+
+    // index all pixels to a pallete color
+
+    pixels = indexPixels(cells, colorMap);
+  }
+
+  /**
+   * Quantize colors by generating a set of cells in normalized L*a*b space
+   * containing all colors. Start with just two cells -- fixed background color
+   * and all others. Keep splitting cells while there are fewer than 256 and
+   * some with multiple colors in them.
+   * 
+   * It is possible that we will end up with fewer than 256 colors.
+   * 
+   * @param tempColors
+   * @return final list of colors
+   */
+  private Lst<ColorCell> quantizeColors(Lst<ColorItem> tempColors) {
+    int n = tempColors.size();
+    Lst<ColorCell> cells = new Lst<ColorCell>();
+    ColorCell cc = new ColorCell(0);
+    cc.addLast(new ColorItem(backgroundColor, true));
+    cells.addLast(cc);
+    cc = new ColorCell(1);
+    if (n > 256)
+      cells.addLast(cc);
+    for (int i = 0; i < n; i++) {
+      ColorItem c = tempColors.get(i);
+      if (c.isBackground)
+        continue;
+      cc.addLast(c);
+      if (n <= 256) {
+        cells.addLast(cc);
+        cc = new ColorCell(cells.size());
+      }
+    }
+    tempColors.clear();
+    if (n > 256)
+      while ((n = cells.size()) < 256) {
+        float maxVol = 0;
+        ColorCell maxCell = null;
+        for (int i = n; --i >= 1;) {
+          ColorCell c = cells.get(i);
+          float v = c.getVolume(false);
+          if (v > maxVol) {
+            maxVol = v;
+            maxCell = c;
+          }
+        }
+        if (maxCell == null || !maxCell.splitCell(cells))
+          break;
+      }
+    return cells;
+  }
+
+  /**
+   * 
+   * Assign all colors to their closest approximation and return an array of
+   * color indexes.
+   * 
+   * Uses Floyd-Steinberg dithering, finding the closest known color and then
+   * spreading out the error over four leading pixels. Limits error to +/- 75
+   * percent in normalized L*a*b space.
+   * 
+   * @param cells
+   *        quantized color cells
+   * @param colorMap
+   *        map of quantized rgb to its cell
+   * @return array of color indexes, one for each pixel
+   * 
+   */
+  private int[] indexPixels(Lst<ColorCell> cells,
+                            Map<Integer, ColorCell> colorMap) {
+    // We need a strip only width+2 wide to process all the errors.
+    // Errors are added to the next pixel and the next row's pixels 
+    // only through p + width + 1:
+    //         p  +1
+    //   +w-1 +w  +w+1
+    // so including p as well, we need a total of width + 2 errors.
+    //
+    // as p moves through the pixels, we just use mod to cycle through
+    // this strip.
+    //
+    int w2 = width + 2;
+    P3[] errors = new P3[w2];
+    // We should replace, not overwrite, pixels 
+    // as this may be the raw canvas.buf32.
+    int[] newPixels = new int[pixels.length];
+    P3 err = new P3();
+    P3 lab;
+    int rgb;
+    Map<Integer, ColorCell> nearestCell = new Hashtable<Integer, ColorCell>();
+    for (int i = 0, p = 0; i < height; ++i) {
+      boolean notLastRow = (i != height - 1);
+      for (int j = 0; j < width; ++j, p++) {
+        if (pixels[p] == backgroundColor) {
+          // leave as 0
+          continue;
+        }
+        P3 pe = errors[p % w2];
+        if (pe == null || pe.x == Float.MAX_VALUE) {
+          lab = null;
+          rgb = pixels[p];
+        } else {
+          lab = toLABnorm(pixels[p]);
+          err = pe;
+          // important not to round the clamp here -- full floating precision
+          err.x = clamp(err.x, -75, 75);
+          err.y = clamp(err.y, -75, 75);
+          err.z = clamp(err.z, -75, 75);
+          lab.add(err);
+          rgb = CU.colorPtToFFRGB(toRGB(lab));
+        }
+        Integer key = Integer.valueOf(rgb);
+        ColorCell cell = colorMap.get(key);
+        if (cell == null) {
+          // critical to generate normalized L*a*b from RGB here for nearestCell mapping.
+          // otherwise future RGB keys may match the wrong cell
+          lab = toLABnorm(rgb);
+          cell = nearestCell.get(key);
+          if (cell == null) {
+            // find nearest cell
+            float maxerr = Float.MAX_VALUE;
+            // skip 0 0 0
+            for (int ib = cells.size(); --ib >= 1;) {
+              ColorCell c = cells.get(ib);
+              err.sub2(lab, c.center);
+              float d = err.lengthSquared();
+              if (d < maxerr) {
+                maxerr = d;
+                cell = c;
+              }
+            }
+            nearestCell.put(key, cell);
+          }
+          if (floydSteinberg) {
+            // dither
+            err.sub2(lab, cell.center);
+            boolean notLastCol = (j < width - 1);
+            if (notLastCol)
+              addError(err, 7, errors, p + 1, w2);
+            if (notLastRow) {
+              if (j > 0)
+                addError(err, 3, errors, p + width - 1, w2);
+              addError(err, 5, errors, p + width, w2);
+              if (notLastCol)
+                addError(err, 1, errors, p + width + 1, w2);
+            }
+          }
+          err.x = Float.MAX_VALUE; // used; flag for resetting to 0
+        }
+        newPixels[p] = cell.index;
+      }
+    }
+    return newPixels;
+  }
+
+  private void addError(P3 err, int f, P3[] errors, int p, int w2) {
+    // GIMP will allow changing the background color.
+    if (pixels[p] == backgroundColor)
+      return;
+    p %= w2;
+    P3 errp = errors[p];
+    if (errp == null)
+      errp = errors[p] = new P3();
+    else if (errp.x == Float.MAX_VALUE) // reuse
+      errp.set(0, 0, 0);
+    errp.scaleAdd2(f / 16f, err, errp);
+  }
+
+  ///////////////////////// CIE L*a*b / XYZ / sRGB conversion methods /////////
+
+  // these could be static, but that just makes for more JavaScript code
+
+  protected P3 toLABnorm(int rgb) {
+    P3 lab = CU.colorPtFromInt(rgb, null);
+    rgbToXyz(lab, lab);
+    xyzToLab(lab, lab);
+    // normalize to 0-100
+    lab.y = (lab.y + 86.185f) / (98.254f + 86.185f) * 100f;
+    lab.z = (lab.z + 107.863f) / (94.482f + 107.863f) * 100f;
+    return lab;
+  }
+
+  protected P3 toRGB(P3 lab) {
+    P3 xyz = P3.newP(lab);
+    // normalized to 0-100
+    xyz.y = xyz.y / 100f * (98.254f + 86.185f) - 86.185f;
+    xyz.z = xyz.z / 100f * (94.482f + 107.863f) - 107.863f;
+    labToXyz(xyz, xyz);
+    return xyzToRgb(xyz, xyz);
+  }
+
+  private static M3 xyz2rgb;
+  private static M3 rgb2xyz;
+
+  static {
+    rgb2xyz = M3.newA9(new float[] { 0.4124f, 0.3576f, 0.1805f, 0.2126f,
+        0.7152f, 0.0722f, 0.0193f, 0.1192f, 0.9505f });
+
+    xyz2rgb = M3.newA9(new float[] { 3.2406f, -1.5372f, -0.4986f, -0.9689f,
+        1.8758f, 0.0415f, 0.0557f, -0.2040f, 1.0570f });
+  }
+
+  public P3 rgbToXyz(P3 rgb, P3 xyz) {
+    // http://en.wikipedia.org/wiki/CIE_1931_color_space
+    // http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
+    if (xyz == null)
+      xyz = new P3();
+    xyz.x = sxyz(rgb.x);
+    xyz.y = sxyz(rgb.y);
+    xyz.z = sxyz(rgb.z);
+    rgb2xyz.rotate(xyz);
+    return xyz;
+  }
+
+  private float sxyz(float x) {
+    x /= 255;
+    return (float) (x <= 0.04045 ? x / 12.92 : Math.pow(((x + 0.055) / 1.055),
+        2.4)) * 100;
+  }
+
+  public P3 xyzToRgb(P3 xyz, P3 rgb) {
+    // http://en.wikipedia.org/wiki/CIE_1931_color_space
+    // http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
+    if (rgb == null)
+      rgb = new P3();
+    rgb.setT(xyz);
+    rgb.scale(0.01f);
+    xyz2rgb.rotate(rgb);
+    rgb.x = clamp(srgb(rgb.x), 0, 255);
+    rgb.y = clamp(srgb(rgb.y), 0, 255);
+    rgb.z = clamp(srgb(rgb.z), 0, 255);
+    return rgb;
+  }
+
+  private float srgb(float x) {
+    return (float) (x > 0.0031308f ? (1.055 * Math.pow(x, 1.0 / 2.4)) - 0.055
+        : x * 12.92) * 255;
+  }
+
+  public P3 xyzToLab(P3 xyz, P3 lab) {
+    // http://en.wikipedia.org/wiki/Lab_color_space
+    // http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
+    // Lab([0..100], [-86.185..98.254], [-107.863..94.482])
+    // XYZn = D65 = {95.0429, 100.0, 108.8900};
+    if (lab == null)
+      lab = new P3();
+    float x = flab(xyz.x / 95.0429f);
+    float y = flab(xyz.y / 100);
+    float z = flab(xyz.z / 108.89f);
+    lab.x = (116 * y) - 16;
+    lab.y = 500 * (x - y);
+    lab.z = 200 * (y - z);
+    return lab;
+  }
+
+  private float flab(float t) {
+    return (float) (t > 8.85645168E-3 /* (24/116)^3 */? Math.pow(t,
+        0.333333333) : 7.78703704 /* 1/3*116/24*116/24 */* t + 0.137931034 /* 16/116 */
+    );
+  }
+
+  public P3 labToXyz(P3 lab, P3 xyz) {
+    // http://en.wikipedia.org/wiki/Lab_color_space
+    // http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
+    // XYZn = D65 = {95.0429, 100.0, 108.8900};
+    if (xyz == null)
+      xyz = new P3();
+
+    xyz.setT(lab);
+    float y = (xyz.x + 16) / 116;
+    float x = xyz.y / 500 + y;
+    float z = y - xyz.z / 200;
+    xyz.x = fxyz(x) * 95.0429f;
+    xyz.y = fxyz(y) * 100;
+    xyz.z = fxyz(z) * 108.89f;
+
+    return xyz;
+  }
+
+  private float fxyz(float t) {
+    return (float) (t > 0.206896552 /* (24/116) */? t * t * t
+        : 0.128418549 /* 3*24/116*24/116 */* (t - 0.137931034 /* 16/116 */));
+  }
+
+  private float clamp(float c, float min, float max) {
+    c = (c < min ? min : c > max ? max : c);
+    return (min == 0 ? Math.round(c) : c);
+  }
+
+  ///////////////////////// GifEncoder writing methods ////////////////////////
+
+  /**
+   * includes logical screen descriptor
+   * 
+   * @throws IOException
+   */
+  private void writeHeader() throws IOException {
+    putString("GIF89a");
+    putWord(width);
+    putWord(height);
+    putByte(0); // no global color table -- using local instead
+    putByte(0); // no background
+    putByte(0); // no pixel aspect ratio given
+  }
+
+  private void writeGraphicControlExtension() {
+    if (isTransparent || delayTime100ths >= 0) {
+      putByte(0x21); // graphic control extension
+      putByte(0xf9); // graphic control label
+      putByte(4); // block size
+      putByte((isTransparent ? 9 : 0) | (delayTime100ths > 0 ? 2 : 0)); // packed bytes 
+      putWord(delayTime100ths > 0 ? delayTime100ths : 0);
+      putByte(0); // transparent index
+      putByte(0); // end-of-block
+    }
+  }
+
+  // see  http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
+  //      +---------------+
+  //   0  |     0x21      |  Extension Label
+  //      +---------------+
+  //   1  |     0xFF      |  Application Extension Label
+  //      +---------------+
+  //   2  |     0x0B      |  Block Size
+  //      +---------------+
+  //   3  |               | 
+  //      +-             -+
+  //   4  |               | 
+  //      +-             -+
+  //   5  |               | 
+  //      +-             -+
+  //   6  |               | 
+  //      +-  NETSCAPE   -+  Application Identifier (8 bytes)
+  //   7  |               | 
+  //      +-             -+
+  //   8  |               | 
+  //      +-             -+
+  //   9  |               | 
+  //      +-             -+
+  //  10  |               | 
+  //      +---------------+
+  //  11  |               | 
+  //      +-             -+
+  //  12  |      2.0      |  Application Authentication Code (3 bytes)
+  //      +-             -+
+  //  13  |               | 
+  //      +===============+                      --+
+  //  14  |     0x03      |  Sub-block Data Size   |
+  //      +---------------+                        |
+  //  15  |     0x01      |  Sub-block ID          |
+  //      +---------------+                        | Application Data Sub-block
+  //  16  |               |                        |
+  //      +-             -+  Loop Count (2 bytes)  |
+  //  17  |               |                        |
+  //      +===============+                      --+
+  //  18  |     0x00      |  Block Terminator
+  //      +---------------+
+
+  private void writeNetscapeLoopExtension() {
+    putByte(0x21); // graphic control extension
+    putByte(0xff); // netscape loop extension
+    putByte(0x0B); // block size
+    putString("NETSCAPE2.0");
+    putByte(3);
+    putByte(1);
+    putWord(0); // loop indefinitely
+    putByte(0); // end-of-block
+
+  }
+
+  private int initCodeSize;
+  private int curpt;
+
+  private void writeImage() {
+    putByte(0x2C);
+    putWord(0); //left
+    putWord(0); //top
+    putWord(width);
+    putWord(height);
+
+    //    <Packed Fields>  =      LISx xZZZ
+
+    //    L Local Color Table Flag
+    //    I Interlace Flag
+    //    S Sort Flag
+    //    x Reserved
+    //    ZZZ Size of Local Color Table
+
+    int packedFields = 0x80 | (interlaced ? 0x40 : 0) | (bitsPerPixel - 1);
+    putByte(packedFields);
+    int colorMapSize = 1 << bitsPerPixel;
+    P3 p = new P3();
+    for (int i = 0; i < colorMapSize; i++) {
+      if (palette[i] != null)
+        p = palette[i];
+      putByte((int) p.x);
+      putByte((int) p.y);
+      putByte((int) p.z);
+    }
+    putByte(initCodeSize = (bitsPerPixel <= 1 ? 2 : bitsPerPixel));
+    compress();
+    putByte(0);
+  }
+
+  private void writeTrailer() {
+    // Write the GIF file terminator
+    putByte(0x3B);
+  }
+
+  ///// compression routines /////
+
+  private static final int EOF = -1;
+
+  // Return the next pixel from the image
+  private int nextPixel() {
+    if (countDown-- == 0)
+      return EOF;
+    int colorIndex = pixels[curpt];
+    // Bump the current X position
+    ++curx;
+    if (curx == width) {
+      // If we are at the end of a scan line, set curx back to the beginning
+      // If we are interlaced, bump the cury to the appropriate spot,
+      // otherwise, just increment it.
+      curx = 0;
+      if (interlaced)
+        updateY(INTERLACE_PARAMS[pass], INTERLACE_PARAMS[pass + 4]);
+      else
+        ++cury;
+    }
+    curpt = cury * width + curx;
+    return colorIndex & 0xff;
+  }
+
+  private static final int[] INTERLACE_PARAMS = { 8, 8, 4, 2, 4, 2, 1, 0 };
+
+  /**
+   * 
+   * Group 1 : Every 8th. row, starting with row 0. (Pass 1)
+   * 
+   * Group 2 : Every 8th. row, starting with row 4. (Pass 2)
+   * 
+   * Group 3 : Every 4th. row, starting with row 2. (Pass 3)
+   * 
+   * Group 4 : Every 2nd. row, starting with row 1. (Pass 4)
+   * 
+   * @param yNext
+   * @param yNew
+   */
+  private void updateY(int yNext, int yNew) {
+    cury += yNext;
+    if (yNew >= 0 && cury >= height) {
+      cury = yNew;
+      ++pass;
+    }
+  }
+
+  // Write out a word to the GIF file
+  private void putWord(int w) {
+    putByte(w);
+    putByte(w >> 8);
+  }
+
+  // GIFCOMPR.C       - GIF Image compression routines
+  //
+  // Lempel-Ziv compression based on 'compress'.  GIF modifications by
+  // David Rowley (mgardi@watdcsu.waterloo.edu)
+
+  // General DEFINEs
+
+  private static final int BITS = 12;
+
+  private static final int HSIZE = 5003; // 80% occupancy
+
+  // GIF Image compression - modified 'compress'
+  //
+  // Based on: compress.c - File compression ala IEEE Computer, June 1984.
+  //
+  // By Authors:  Spencer W. Thomas      (decvax!harpo!utah-cs!utah-gr!thomas)
+  //              Jim McKie              (decvax!mcvax!jim)
+  //              Steve Davies           (decvax!vax135!petsd!peora!srd)
+  //              Ken Turkowski          (decvax!decwrl!turtlevax!ken)
+  //              James A. Woods         (decvax!ihnp4!ames!jaw)
+  //              Joe Orost              (decvax!vax135!petsd!joe)
+
+  private int nBits; // number of bits/code
+  private int maxbits = BITS; // user settable max # bits/code
+  private int maxcode; // maximum code, given n_bits
+  private int maxmaxcode = 1 << BITS; // should NEVER generate this code
+
+  private final static int MAXCODE(int nBits) {
+    return (1 << nBits) - 1;
+  }
+
+  private int[] htab = new int[HSIZE];
+  private int[] codetab = new int[HSIZE];
+
+  private int hsize = HSIZE; // for dynamic table sizing
+
+  private int freeEnt = 0; // first unused entry
+
+  // block compression parameters -- after all codes are used up,
+  // and compression rate changes, start over.
+  private boolean clearFlag = false;
+
+  // Algorithm:  use open addressing double hashing (no chaining) on the
+  // prefix code / next character combination.  We do a variant of Knuth's
+  // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
+  // secondary probe.  Here, the modular division first probe is gives way
+  // to a faster exclusive-or manipulation.  Also do block compression with
+  // an adaptive reset, whereby the code table is cleared when the compression
+  // ratio decreases, but after the table fills.  The variable-length output
+  // codes are re-sized at this point, and a special CLEAR code is generated
+  // for the decompressor.  Late addition:  construct the table according to
+  // file size for noticeable speed improvement on small files.  Please direct
+  // questions about this implementation to ames!jaw.
+
+  private int clearCode;
+  private int EOFCode;
+
+  private int countDown;
+  private int pass = 0;
+  private int curx, cury;
+
+  private void compress() {
+
+    // Calculate number of bits we are expecting
+    countDown = width * height;
+
+    // Indicate which pass we are on (if interlace)
+    pass = 0;
+    // Set up the current x and y position
+    curx = 0;
+    cury = 0;
+
+    // Set up the necessary values
+    clearFlag = false;
+    nBits = initCodeSize + 1;
+    maxcode = MAXCODE(nBits);
+
+    clearCode = 1 << initCodeSize;
+    EOFCode = clearCode + 1;
+    freeEnt = clearCode + 2;
+
+    // Set up the 'byte output' routine
+    bufPt = 0;
+
+    int ent = nextPixel();
+
+    int hshift = 0;
+    int fcode;
+    for (fcode = hsize; fcode < 65536; fcode *= 2)
+      ++hshift;
+    hshift = 8 - hshift; // set hash code range bound
+
+    int hsizeReg = hsize;
+    clearHash(hsizeReg); // clear hash table
+
+    output(clearCode);
+
+    int c;
+    outer_loop: while ((c = nextPixel()) != EOF) {
+      fcode = (c << maxbits) + ent;
+      int i = (c << hshift) ^ ent; // xor hashing
+
+      if (htab[i] == fcode) {
+        ent = codetab[i];
+        continue;
+      } else if (htab[i] >= 0) // non-empty slot
+      {
+        int disp = hsizeReg - i; // secondary hash (after G. Knott)
+        if (i == 0)
+          disp = 1;
+        do {
+          if ((i -= disp) < 0)
+            i += hsizeReg;
+
+          if (htab[i] == fcode) {
+            ent = codetab[i];
+            continue outer_loop;
+          }
+        } while (htab[i] >= 0);
+      }
+      output(ent);
+      ent = c;
+      if (freeEnt < maxmaxcode) {
+        codetab[i] = freeEnt++; // code -> hashtable
+        htab[i] = fcode;
+      } else {
+        clearBlock();
+      }
+    }
+    // Put out the final code.
+    output(ent);
+    output(EOFCode);
+  }
+
+  // output
+  //
+  // Output the given code.
+  // Inputs:
+  //      code:   A n_bits-bit integer.  If == -1, then EOF.  This assumes
+  //              that n_bits =< wordsize - 1.
+  // Outputs:
+  //      Outputs code to the file.
+  // Assumptions:
+  //      Chars are 8 bits long.
+  // Algorithm:
+  //      Maintain a BITS character long buffer (so that 8 codes will
+  // fit in it exactly).  Use the VAX insv instruction to insert each
+  // code in turn.  When the buffer fills up empty it and start over.
+
+  private int curAccum = 0;
+  private int curBits = 0;
+
+  private int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
+      0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF,
+      0x7FFF, 0xFFFF };
+
+  private void output(int code) {
+    curAccum &= masks[curBits];
+
+    if (curBits > 0)
+      curAccum |= (code << curBits);
+    else
+      curAccum = code;
+
+    curBits += nBits;
+
+    while (curBits >= 8) {
+      byteOut((byte) (curAccum & 0xff));
+      curAccum >>= 8;
+      curBits -= 8;
+    }
+
+    // If the next entry is going to be too big for the code size,
+    // then increase it, if possible.
+    if (freeEnt > maxcode || clearFlag) {
+      if (clearFlag) {
+        maxcode = MAXCODE(nBits = initCodeSize + 1);
+        clearFlag = false;
+      } else {
+        ++nBits;
+        if (nBits == maxbits)
+          maxcode = maxmaxcode;
+        else
+          maxcode = MAXCODE(nBits);
+      }
+    }
+
+    if (code == EOFCode) {
+      // At EOF, write the rest of the buffer.
+      while (curBits > 0) {
+        byteOut((byte) (curAccum & 0xff));
+        curAccum >>= 8;
+        curBits -= 8;
+      }
+      flushBytes();
+    }
+  }
+
+  // Clear out the hash table
+
+  // table clear for block compress
+  private void clearBlock() {
+    clearHash(hsize);
+    freeEnt = clearCode + 2;
+    clearFlag = true;
+
+    output(clearCode);
+  }
+
+  // reset code table
+  private void clearHash(int hsize) {
+    for (int i = 0; i < hsize; ++i)
+      htab[i] = -1;
+  }
+
+  // GIF-specific routines (byte array buffer)
+
+  // Number of bytes so far in this 'packet'
+  private int bufPt;
+
+  // Define the storage for the packet accumulator
+  final private byte[] buf = new byte[256];
+
+  // Add a byte to the end of the current packet, and if it is 254
+  // byte, flush the packet to disk.
+  private void byteOut(byte c) {
+    buf[bufPt++] = c;
+    if (bufPt >= 254)
+      flushBytes();
+  }
+
+  // Flush the packet to disk, and reset the accumulator
+  protected void flushBytes() {
+    if (bufPt > 0) {
+      putByte(bufPt);
+      out.write(buf, 0, bufPt);
+      byteCount += bufPt;
+      bufPt = 0;
+    }
+  }
+
+}
diff --git a/src/javajs/img/ImageEncoder.java b/src/javajs/img/ImageEncoder.java
new file mode 100644 (file)
index 0000000..7781202
--- /dev/null
@@ -0,0 +1,127 @@
+/* $RCSfile$
+ * $Author: hansonr $
+ * $Date: 2007-06-02 12:14:13 -0500 (Sat, 02 Jun 2007) $
+ * $Revision: 7831 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2000-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+// ImageEncoder - abstract class for writing out an image
+//
+// Copyright (C) 1996 by Jef Poskanzer <jef@mail.acme.com>.  All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+//    notice, this list of conditions and the following disclaimer in the
+//    documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+// SUCH DAMAGE.
+//
+// Visit the ACME Labs Java page for up-to-date versions of this and other
+// fine Java utilities: http://www.acme.com/java/
+
+package javajs.img;
+
+import java.util.Map;
+
+import javajs.api.GenericImageEncoder;
+import javajs.util.OC;
+
+
+
+/**
+ * Generic abstract image creator:
+ * 
+ *   (1) set parameters
+ *   
+ *   (2) encode the image bytes, if necessary
+ *   
+ *   (3) generate the image 
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+
+public abstract class ImageEncoder implements GenericImageEncoder {
+
+  protected OC out;
+
+  protected int width = -1;
+  protected int height = -1;
+  protected int quality = -1;
+  protected String date;
+  protected boolean logging;
+  protected boolean doClose = true;
+
+  /**
+   * @param type
+   * @param out
+   * @param params
+   */
+  @Override
+  public boolean createImage(String type, OC out, Map<String, Object> params)
+      throws Exception {
+    this.out = out;
+    logging = (Boolean.TRUE == params.get("logging"));
+    width = ((Integer) params.get("imageWidth")).intValue();
+    height = ((Integer) params.get("imageHeight")).intValue();
+    pixels = (int[]) params.get("imagePixels");
+    date = (String) params.get("date");
+    Integer q = (Integer) params.get("quality");
+    quality = (q == null ? -1 : q.intValue());
+    setParams(params);
+    generate();
+    close(); // GIF will override this and not close
+    return doClose;
+  }
+
+  abstract protected void setParams(Map<String, Object> params);
+  abstract protected void generate() throws Exception;
+
+  protected int[] pixels;
+
+  protected void putString(String s) {
+    byte[] b = s.getBytes();
+    out.write(b, 0, b.length);
+  }
+
+  protected void putByte(int b) {
+    out.writeByteAsInt(b);
+  }
+  
+  protected void close() {
+    // your responsibility to close the output channel
+  }
+
+}
diff --git a/src/javajs/img/Jpg64Encoder.java b/src/javajs/img/Jpg64Encoder.java
new file mode 100644 (file)
index 0000000..796972f
--- /dev/null
@@ -0,0 +1,64 @@
+// Version 1.0a
+// Copyright (C) 1998, James R. Weeks and BioElectroMech.
+// Visit BioElectroMech at www.obrador.com.  Email James@obrador.com.
+
+// See license.txt for details about the allowed used of this software.
+// This software is based in part on the work of the Independent JPEG Group.
+// See IJGreadme.txt for details about the Independent JPEG Group's license.
+
+// This encoder is inspired by the Java Jpeg encoder by Florian Raemy,
+// studwww.eurecom.fr/~raemy.
+// It borrows a great deal of code and structure from the Independent
+// Jpeg Group's Jpeg 6a library, Copyright Thomas G. Lane.
+// See license.txt for details 
+
+/*
+ * JpegEncoder and its associated classes are Copyright (c) 1998, James R. Weeks and BioElectroMech
+ * see(Jmol/src/com/obrador/license.txt)
+ * 
+ * javajs.img.JpegEncoder.java was adapted by Bob Hanson
+ * for Jmol in the following ways:
+ * 
+ * 1) minor coding efficiencies were made in some for() loops.
+ * 2) methods not used by Jmol were commented out
+ * 3) method and variable signatures were modified to provide 
+ *    more appropriate method privacy.
+ * 4) additions for Java2Script compatibility 
+ * 
+ * Original files are maintained in the Jmol.src.com.obrador package, but
+ * these original files are not distributed with Jmol.
+ *   
+*/
+
+package javajs.img;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javajs.util.Base64;
+import javajs.util.OC;
+
+
+public class Jpg64Encoder extends JpgEncoder {
+
+  private OC outTemp;
+
+  @Override
+  protected void setParams(Map<String, Object> params) {
+    defaultQuality = 75;
+    outTemp = (OC) params.remove("outputChannelTemp");
+    super.setParams(params);
+  }
+
+  @Override
+  protected void generate() throws IOException {
+    OC out0 = out;
+    out = outTemp;
+    super.generate();
+    byte[] bytes = Base64.getBytes64(out.toByteArray());
+    outTemp = null;
+    out = out0;
+    out.write(bytes, 0, bytes.length);
+  }
+
+}
diff --git a/src/javajs/img/JpgEncoder.java b/src/javajs/img/JpgEncoder.java
new file mode 100644 (file)
index 0000000..4e49718
--- /dev/null
@@ -0,0 +1,1208 @@
+// Version 1.0a
+// Copyright (C) 1998, James R. Weeks and BioElectroMech.
+// Visit BioElectroMech at www.obrador.com.  Email James@obrador.com.
+
+// See license.txt for details about the allowed used of this software.
+// This software is based in part on the work of the Independent JPEG Group.
+// See IJGreadme.txt for details about the Independent JPEG Group's license.
+
+// This encoder is inspired by the Java Jpeg encoder by Florian Raemy,
+// studwww.eurecom.fr/~raemy.
+// It borrows a great deal of code and structure from the Independent
+// Jpeg Group's Jpeg 6a library, Copyright Thomas G. Lane.
+// See license.txt for details 
+
+/*
+ * JpegEncoder and its associated classes are Copyright (c) 1998, James R. Weeks and BioElectroMech
+ * see(Jmol/src/com/obrador/license.txt)
+ * 
+ * javjs.img.JpegEncoder.java was adapted by Bob Hanson
+ * 
+ * for Jmol in the following ways:
+ * 
+ * 1) minor coding efficiencies were made in some for() loops.
+ * 2) methods not used by Jmol were commented out
+ * 3) method and variable signatures were modified to provide 
+ *    more appropriate method privacy.
+ * 4) additions for Java2Script compatibility 
+ * 
+ * Original files are maintained in the Jmol.src.com.obrador package, but
+ * these original files are not distributed with Jmol.
+ *   
+*/
+
+package javajs.img;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javajs.img.ImageEncoder;
+import javajs.util.AU;
+import javajs.util.OC;
+
+/**
+ * JpegEncoder - The JPEG main program which performs a jpeg compression of an
+ * image.
+ * 
+ *  A system to allow the full Jmol state -- regardless of length -- 
+ *  to be encoded in a set of APP1 (FFE1) tags.
+ *  But we have to be careful about line ends for backward compatibility. 
+ *  This solution is not 100% effective, because some data lines may in principle be 
+ *  Very large and may not contain new lines for more than 65500 characters, 
+ *  But that would be very unusual. Perhaps a huge data set loaded from a 
+ *  string. Introduced in Jmol 12.1.36. Bob Hanson
+ *  
+ * See org.com.obrador.license.txt
+ * 
+ */
+
+public class JpgEncoder extends ImageEncoder {
+
+  // this string will GENERALLY appear at the end of lines and be escaped 
+  private static final int CONTINUE_MAX = 65500; // some room to spare here. 
+  private static final int CONTINUE_MAX_BUFFER = CONTINUE_MAX + 10; // never break up last 10 bytes
+
+  private JpegObj jpegObj;
+  private Huffman huf;
+  private DCT dct;
+  protected int defaultQuality = 100;
+  private String applicationTag;
+
+  public JpgEncoder() {
+
+  }
+
+  @Override
+  protected void setParams(Map<String, Object> params) {
+    if (quality <= 0)
+      quality = (params.containsKey("qualityJPG") ? ((Integer) params.get("qualityJPG")).intValue() : defaultQuality);
+    jpegObj = new JpegObj();
+    jpegObj.comment = (String) params.get("comment");
+    applicationTag = (String) params.get("jpgAppTag");
+  }
+
+  @Override
+  protected void generate() throws IOException {
+    jpegObj.imageWidth = width;
+    jpegObj.imageHeight = height;
+    dct = new DCT(quality);
+    huf = new Huffman(width, height);
+    if (jpegObj == null)
+      return;
+    jpegObj.getYCCArray(pixels);
+    String longState = writeHeaders(jpegObj, dct);
+    writeCompressedData(jpegObj, dct, huf);
+    writeMarker(eoi);
+    if (longState != null) {
+      byte[] b = longState.getBytes();
+      out.write(b, 0, b.length);
+    }
+  }
+
+  private void writeCompressedData(JpegObj jpegObj, DCT dct, Huffman huf) {
+    int i, j, r, c, a, b;
+    int comp, xpos, ypos, xblockoffset, yblockoffset;
+    float inputArray[][];
+    float dctArray1[][] = new float[8][8];
+    double dctArray2[][] = new double[8][8];
+    int dctArray3[] = new int[8 * 8];
+
+    /*
+     * This method controls the compression of the image.
+     * Starting at the upper left of the image, it compresses 8x8 blocks
+     * of data until the entire image has been compressed.
+     */
+
+    int lastDCvalue[] = new int[jpegObj.numberOfComponents];
+    //int zeroArray[] = new int[64]; // initialized to hold all zeros
+    //int Width = 0, Height = 0;
+    //int nothing = 0, not;
+    int minBlockWidth, minBlockHeight;
+    // This initial setting of MinBlockWidth and MinBlockHeight is done to
+    // ensure they start with values larger than will actually be the case.
+    minBlockWidth = ((huf.imageWidth % 8 != 0) ? (int) (Math
+        .floor(huf.imageWidth / 8.0) + 1) * 8 : huf.imageWidth);
+    minBlockHeight = ((huf.imageHeight % 8 != 0) ? (int) (Math
+        .floor(huf.imageHeight / 8.0) + 1) * 8 : huf.imageHeight);
+    for (comp = 0; comp < jpegObj.numberOfComponents; comp++) {
+      minBlockWidth = Math.min(minBlockWidth, jpegObj.blockWidth[comp]);
+      minBlockHeight = Math.min(minBlockHeight, jpegObj.blockHeight[comp]);
+    }
+    xpos = 0;
+    for (r = 0; r < minBlockHeight; r++) {
+      for (c = 0; c < minBlockWidth; c++) {
+        xpos = c * 8;
+        ypos = r * 8;
+        for (comp = 0; comp < jpegObj.numberOfComponents; comp++) {
+          //Width = JpegObj.BlockWidth[comp];
+          //Height = JpegObj.BlockHeight[comp];
+          inputArray = jpegObj.components[comp];
+          int vsampF = jpegObj.vsampFactor[comp];
+          int hsampF = jpegObj.hsampFactor[comp];
+          int qNumber = jpegObj.qtableNumber[comp];
+          int dcNumber = jpegObj.dctableNumber[comp];
+          int acNumber = jpegObj.actableNumber[comp];
+
+          for (i = 0; i < vsampF; i++) {
+            for (j = 0; j < hsampF; j++) {
+              xblockoffset = j * 8;
+              yblockoffset = i * 8;
+              for (a = 0; a < 8; a++) {
+                for (b = 0; b < 8; b++) {
+
+                  // I believe this is where the dirty line at the bottom of
+                  // the image is coming from.
+                  // I need to do a check here to make sure I'm not reading past
+                  // image data.
+                  // This seems to not be a big issue right now. (04/04/98)
+
+                  dctArray1[a][b] = inputArray[ypos + yblockoffset + a][xpos
+                      + xblockoffset + b];
+                }
+              }
+              // The following code commented out because on some images this technique
+              // results in poor right and bottom borders.
+              // if ((!JpegObj.lastColumnIsDummy[comp] || c < Width - 1) &&
+              //       (!JpegObj.lastRowIsDummy[comp] || r < Height - 1)) {
+              dctArray2 = DCT.forwardDCT(dctArray1);
+              dctArray3 = DCT.quantizeBlock(dctArray2, dct.divisors[qNumber]);
+              // }
+              // else {
+              //   zeroArray[0] = dctArray3[0];
+              //   zeroArray[0] = lastDCvalue[comp];
+              //   dctArray3 = zeroArray;
+              // }
+              huf.HuffmanBlockEncoder(out, dctArray3, lastDCvalue[comp],
+                  dcNumber, acNumber);
+              lastDCvalue[comp] = dctArray3[0];
+            }
+          }
+        }
+      }
+    }
+    huf.flushBuffer(out);
+  }
+
+  private static byte[] eoi = { (byte) 0xFF, (byte) 0xD9 };
+
+  private static byte[] jfif = new byte[] {
+  /* JFIF[0] =*/(byte) 0xff,
+  /* JFIF[1] =*/(byte) 0xe0,
+  /* JFIF[2] =*/0,
+  /* JFIF[3] =*/16,
+  /* JFIF[4] =*/(byte) 0x4a, //'J'
+      /* JFIF[5] =*/(byte) 0x46, //'F'
+      /* JFIF[6] =*/(byte) 0x49, //'I'
+      /* JFIF[7] =*/(byte) 0x46, //'F'
+      /* JFIF[8] =*/0,
+      /* JFIF[9] =*/1,
+      /* JFIF[10] =*/0,
+      /* JFIF[11] =*/0,
+      /* JFIF[12] =*/0,
+      /* JFIF[13] =*/1,
+      /* JFIF[14] =*/0,
+      /* JFIF[15] =*/1,
+      /* JFIF[16] =*/0,
+      /* JFIF[17] =*/0 };
+
+  private static byte[] soi = { (byte) 0xFF, (byte) 0xD8 };
+
+  private String writeHeaders(JpegObj jpegObj, DCT dct) {
+    int i, j, index, offset;
+    int tempArray[];
+
+    // the SOI marker
+    writeMarker(soi);
+
+    // The order of the following headers is quite inconsequential.
+    // the JFIF header
+    writeArray(jfif);
+
+    // Comment Header
+    String comment = null;
+    if (jpegObj.comment != null && jpegObj.comment.length() > 0)
+      writeString(jpegObj.comment, (byte) 0xE1); // App data 1
+    writeString(
+        "JPEG Encoder Copyright 1998, James R. Weeks and BioElectroMech.\n\n",
+        (byte) 0xFE);
+
+    // The DQT header
+    // 0 is the luminance index and 1 is the chrominance index
+    byte dqt[] = new byte[134];
+    dqt[0] = (byte) 0xFF;
+    dqt[1] = (byte) 0xDB;
+    dqt[2] = 0;
+    dqt[3] = (byte) 132;
+    offset = 4;
+    for (i = 0; i < 2; i++) {
+      dqt[offset++] = (byte) ((0 << 4) + i);
+      tempArray = dct.quantum[i];
+      for (j = 0; j < 64; j++) {
+        dqt[offset++] = (byte) tempArray[Huffman.jpegNaturalOrder[j]];
+      }
+    }
+    writeArray(dqt);
+
+    // Start of Frame Header
+    byte sof[] = new byte[19];
+    sof[0] = (byte) 0xFF;
+    sof[1] = (byte) 0xC0;
+    sof[2] = 0;
+    sof[3] = 17;
+    sof[4] = (byte) jpegObj.precision;
+    sof[5] = (byte) ((jpegObj.imageHeight >> 8) & 0xFF);
+    sof[6] = (byte) ((jpegObj.imageHeight) & 0xFF);
+    sof[7] = (byte) ((jpegObj.imageWidth >> 8) & 0xFF);
+    sof[8] = (byte) ((jpegObj.imageWidth) & 0xFF);
+    sof[9] = (byte) jpegObj.numberOfComponents;
+    index = 10;
+    for (i = 0; i < sof[9]; i++) {
+      sof[index++] = (byte) jpegObj.compID[i];
+      sof[index++] = (byte) ((jpegObj.hsampFactor[i] << 4) + jpegObj.vsampFactor[i]);
+      sof[index++] = (byte) jpegObj.qtableNumber[i];
+    }
+    writeArray(sof);
+
+    WriteDHTHeader(Huffman.bitsDCluminance, Huffman.valDCluminance);
+    WriteDHTHeader(Huffman.bitsACluminance, Huffman.valACluminance);
+    WriteDHTHeader(Huffman.bitsDCchrominance, Huffman.valDCchrominance);
+    WriteDHTHeader(Huffman.bitsACchrominance, Huffman.valACchrominance);
+
+    // Start of Scan Header
+    byte sos[] = new byte[14];
+    sos[0] = (byte) 0xFF;
+    sos[1] = (byte) 0xDA;
+    sos[2] = 0;
+    sos[3] = 12;
+    sos[4] = (byte) jpegObj.numberOfComponents;
+    index = 5;
+    for (i = 0; i < sos[4]; i++) {
+      sos[index++] = (byte) jpegObj.compID[i];
+      sos[index++] = (byte) ((jpegObj.dctableNumber[i] << 4) + jpegObj.actableNumber[i]);
+    }
+    sos[index++] = (byte) jpegObj.ss;
+    sos[index++] = (byte) jpegObj.se;
+    sos[index++] = (byte) ((jpegObj.ah << 4) + jpegObj.al);
+    writeArray(sos);
+    return comment;
+  }
+
+  private void writeString(String s, byte id) {
+    int len = s.length();
+    int i0 = 0;
+    String suffix = applicationTag;
+    while (i0 < len) {
+      int nBytes = len - i0;
+      if (nBytes > CONTINUE_MAX_BUFFER) {
+        nBytes = CONTINUE_MAX;
+        // but break only at line breaks
+        int pt = s.lastIndexOf('\n', i0 + nBytes);
+        if (pt > i0 + 1)
+          nBytes = pt - i0;
+      }
+      if (i0 + nBytes == len)
+        suffix = "";
+      writeTag(nBytes + suffix.length(), id);
+      writeArray(s.substring(i0, i0 + nBytes).getBytes());
+      if (suffix.length() > 0)
+        writeArray(suffix.getBytes());
+      i0 += nBytes;
+    }
+  }
+
+  private void writeTag(int length, byte id) {
+    length += 2;
+    byte com[] = new byte[4];
+    com[0] = (byte) 0xFF;
+    com[1] = id;
+    com[2] = (byte) ((length >> 8) & 0xFF);
+    com[3] = (byte) (length & 0xFF);
+    writeArray(com);
+  }
+
+  void WriteDHTHeader(int[] bits, int[] val) {
+    // hansonr@stolaf.edu: simplified code.
+    byte[] dht;
+    int bytes = 0;
+    for (int j = 1; j < 17; j++)
+      bytes += bits[j];
+    dht = new byte[21 + bytes];
+    dht[0] = (byte) 0xFF;
+    dht[1] = (byte) 0xC4;
+    int index = 4;
+    for (int j = 0; j < 17; j++)
+      dht[index++] = (byte) bits[j];
+    for (int j = 0; j < bytes; j++)
+      dht[index++] = (byte) val[j];
+    dht[2] = (byte) (((index - 2) >> 8) & 0xFF);
+    dht[3] = (byte) ((index - 2) & 0xFF);
+    writeArray(dht);
+  }
+
+  void writeMarker(byte[] data) {
+    out.write(data, 0, 2);
+  }
+
+  void writeArray(byte[] data) {
+    out.write(data, 0, data.length);
+  }
+
+}
+
+// This class incorporates quality scaling as implemented in the JPEG-6a
+// library.
+
+/*
+ * DCT - A Java implementation of the Discreet Cosine Transform
+ */
+
+class DCT {
+
+  /**
+   * DCT Block Size - default 8
+   */
+  private final static int N = 8;
+  private final static int NN = N * N;
+
+  /**
+   * Image Quality (0-100) - default 80 (good image / good compression)
+   */
+  //public int QUALITY = 80;
+
+  int[][] quantum = AU.newInt2(2);
+  double[][] divisors = AU.newDouble2(2);
+
+  /**
+   * Quantitization Matrix for luminace.
+   */
+  private int quantum_luminance[] = new int[NN];
+  private double DivisorsLuminance[] = new double[NN];
+
+  /**
+   * Quantitization Matrix for chrominance.
+   */
+  private int quantum_chrominance[] = new int[NN];
+  private double DivisorsChrominance[] = new double[NN];
+
+  /**
+   * Constructs a new DCT object. Initializes the cosine transform matrix these
+   * are used when computing the DCT and it's inverse. This also initializes the
+   * run length counters and the ZigZag sequence. Note that the image quality
+   * can be worse than 25 however the image will be extemely pixelated, usually
+   * to a block size of N.
+   * 
+   * @param quality
+   *        The quality of the image (0 worst - 100 best)
+   * 
+   */
+  DCT(int quality) {
+    initMatrix(quality);
+  }
+
+  /*
+   * This method sets up the quantization matrix for luminance and
+   * chrominance using the Quality parameter.
+   */
+  private void initMatrix(int quality) {
+    // converting quality setting to that specified in the jpeg_quality_scaling
+    // method in the IJG Jpeg-6a C libraries
+
+    quality = (quality < 1 ? 1 : quality > 100 ? 100 : quality);
+    quality = (quality < 50 ? 5000 / quality : 200 - quality * 2);
+
+    // Creating the luminance matrix
+
+    quantum_luminance[0] = 16;
+    quantum_luminance[1] = 11;
+    quantum_luminance[2] = 10;
+    quantum_luminance[3] = 16;
+    quantum_luminance[4] = 24;
+    quantum_luminance[5] = 40;
+    quantum_luminance[6] = 51;
+    quantum_luminance[7] = 61;
+    quantum_luminance[8] = 12;
+    quantum_luminance[9] = 12;
+    quantum_luminance[10] = 14;
+    quantum_luminance[11] = 19;
+    quantum_luminance[12] = 26;
+    quantum_luminance[13] = 58;
+    quantum_luminance[14] = 60;
+    quantum_luminance[15] = 55;
+    quantum_luminance[16] = 14;
+    quantum_luminance[17] = 13;
+    quantum_luminance[18] = 16;
+    quantum_luminance[19] = 24;
+    quantum_luminance[20] = 40;
+    quantum_luminance[21] = 57;
+    quantum_luminance[22] = 69;
+    quantum_luminance[23] = 56;
+    quantum_luminance[24] = 14;
+    quantum_luminance[25] = 17;
+    quantum_luminance[26] = 22;
+    quantum_luminance[27] = 29;
+    quantum_luminance[28] = 51;
+    quantum_luminance[29] = 87;
+    quantum_luminance[30] = 80;
+    quantum_luminance[31] = 62;
+    quantum_luminance[32] = 18;
+    quantum_luminance[33] = 22;
+    quantum_luminance[34] = 37;
+    quantum_luminance[35] = 56;
+    quantum_luminance[36] = 68;
+    quantum_luminance[37] = 109;
+    quantum_luminance[38] = 103;
+    quantum_luminance[39] = 77;
+    quantum_luminance[40] = 24;
+    quantum_luminance[41] = 35;
+    quantum_luminance[42] = 55;
+    quantum_luminance[43] = 64;
+    quantum_luminance[44] = 81;
+    quantum_luminance[45] = 104;
+    quantum_luminance[46] = 113;
+    quantum_luminance[47] = 92;
+    quantum_luminance[48] = 49;
+    quantum_luminance[49] = 64;
+    quantum_luminance[50] = 78;
+    quantum_luminance[51] = 87;
+    quantum_luminance[52] = 103;
+    quantum_luminance[53] = 121;
+    quantum_luminance[54] = 120;
+    quantum_luminance[55] = 101;
+    quantum_luminance[56] = 72;
+    quantum_luminance[57] = 92;
+    quantum_luminance[58] = 95;
+    quantum_luminance[59] = 98;
+    quantum_luminance[60] = 112;
+    quantum_luminance[61] = 100;
+    quantum_luminance[62] = 103;
+    quantum_luminance[63] = 99;
+
+    AANscale(DivisorsLuminance, quantum_luminance, quality);
+
+    // Creating the chrominance matrix
+
+    for (int i = 4; i < 64; i++)
+      quantum_chrominance[i] = 99;
+
+    quantum_chrominance[0] = 17;
+    quantum_chrominance[1] = 18;
+    quantum_chrominance[2] = 24;
+    quantum_chrominance[3] = 47;
+
+    quantum_chrominance[8] = 18;
+    quantum_chrominance[9] = 21;
+    quantum_chrominance[10] = 26;
+    quantum_chrominance[11] = 66;
+
+    quantum_chrominance[16] = 24;
+    quantum_chrominance[17] = 26;
+    quantum_chrominance[18] = 56;
+
+    quantum_chrominance[24] = 47;
+    quantum_chrominance[25] = 66;
+
+    AANscale(DivisorsChrominance, quantum_chrominance, quality);
+
+    // quantum and Divisors are objects used to hold the appropriate matices
+
+    quantum[0] = quantum_luminance;
+    quantum[1] = quantum_chrominance;
+
+    divisors[0] = DivisorsLuminance;
+    divisors[1] = DivisorsChrominance;
+
+  }
+
+  private final static double[] AANscaleFactor = { 1.0, 1.387039845,
+      1.306562965, 1.175875602, 1.0, 0.785694958, 0.541196100, 0.275899379 };
+
+  static private void AANscale(double[] divisors, int[] values, int quality) {
+
+    for (int j = 0; j < 64; j++) {
+      int temp = (values[j] * quality + 50) / 100;
+      values[j] = (temp < 1 ? 1 : temp > 255 ? 255 : temp);
+    }
+
+    for (int i = 0, index = 0; i < 8; i++)
+      for (int j = 0; j < 8; j++, index++)
+        // The divisors for the LL&M method (the slow integer method used in
+        // jpeg 6a library).  This method is currently (04/04/98) incompletely
+        // implemented.
+        // DivisorsLuminance[index] = ((double) quantum_luminance[index]) << 3;
+        // The divisors for the AAN method (the float method used in jpeg 6a library.
+        divisors[index] = (0.125 / (values[index] * AANscaleFactor[i] * AANscaleFactor[j]));
+  }
+
+  /*
+   * This method preforms forward DCT on a block of image data using
+   * the literal method specified for a 2-D Discrete Cosine Transform.
+   * It is included as a curiosity and can give you an idea of the
+   * difference in the compression result (the resulting image quality)
+   * by comparing its output to the output of the AAN method below.
+   * It is ridiculously inefficient.
+   */
+
+  // For now the final output is unusable.  The associated quantization step
+  // needs some tweaking.  If you get this part working, please let me know.
+  /*
+    public double[][] forwardDCTExtreme(float input[][])
+    {
+      double output[][] = new double[N][N];
+      int v, u, x, y;
+      for (v = 0; v < 8; v++) {
+        for (u = 0; u < 8; u++) {
+          for (x = 0; x < 8; x++) {
+            for (y = 0; y < 8; y++) {
+              output[v][u] += input[x][y] * 
+                  Math.cos(((double)(2*x + 1)*(double)u*Math.PI)/16)*
+                  Math.cos(((double)(2*y + 1)*(double)v*Math.PI)/16);
+            }
+          }
+          output[v][u] *= (0.25)*((u == 0) ? (1.0/Math.sqrt(2)) : (double) 1.0)*((v == 0) ? (1.0/Math.sqrt(2)) : (double) 1.0);
+        }
+      }
+      return output;
+    }
+    
+  */
+  /*
+   * This method preforms a DCT on a block of image data using the AAN
+   * method as implemented in the IJG Jpeg-6a library.
+   */
+  static double[][] forwardDCT(float input[][]) {
+    double output[][] = new double[N][N];
+    double tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
+    double tmp10, tmp11, tmp12, tmp13;
+    double z1, z2, z3, z4, z5, z11, z13;
+    // Subtracts 128 from the input values
+    for (int i = 0; i < 8; i++)
+      for (int j = 0; j < 8; j++)
+        output[i][j] = (input[i][j] - 128.0);
+    // input[i][j] -= 128;
+
+    for (int i = 0; i < 8; i++) {
+      tmp0 = output[i][0] + output[i][7];
+      tmp7 = output[i][0] - output[i][7];
+      tmp1 = output[i][1] + output[i][6];
+      tmp6 = output[i][1] - output[i][6];
+      tmp2 = output[i][2] + output[i][5];
+      tmp5 = output[i][2] - output[i][5];
+      tmp3 = output[i][3] + output[i][4];
+      tmp4 = output[i][3] - output[i][4];
+
+      tmp10 = tmp0 + tmp3;
+      tmp13 = tmp0 - tmp3;
+      tmp11 = tmp1 + tmp2;
+      tmp12 = tmp1 - tmp2;
+
+      output[i][0] = tmp10 + tmp11;
+      output[i][4] = tmp10 - tmp11;
+
+      z1 = (tmp12 + tmp13) * 0.707106781;
+      output[i][2] = tmp13 + z1;
+      output[i][6] = tmp13 - z1;
+
+      tmp10 = tmp4 + tmp5;
+      tmp11 = tmp5 + tmp6;
+      tmp12 = tmp6 + tmp7;
+
+      z5 = (tmp10 - tmp12) * 0.382683433;
+      z2 = 0.541196100 * tmp10 + z5;
+      z4 = 1.306562965 * tmp12 + z5;
+      z3 = tmp11 * 0.707106781;
+
+      z11 = tmp7 + z3;
+      z13 = tmp7 - z3;
+
+      output[i][5] = z13 + z2;
+      output[i][3] = z13 - z2;
+      output[i][1] = z11 + z4;
+      output[i][7] = z11 - z4;
+    }
+
+    for (int i = 0; i < 8; i++) {
+      tmp0 = output[0][i] + output[7][i];
+      tmp7 = output[0][i] - output[7][i];
+      tmp1 = output[1][i] + output[6][i];
+      tmp6 = output[1][i] - output[6][i];
+      tmp2 = output[2][i] + output[5][i];
+      tmp5 = output[2][i] - output[5][i];
+      tmp3 = output[3][i] + output[4][i];
+      tmp4 = output[3][i] - output[4][i];
+
+      tmp10 = tmp0 + tmp3;
+      tmp13 = tmp0 - tmp3;
+      tmp11 = tmp1 + tmp2;
+      tmp12 = tmp1 - tmp2;
+
+      output[0][i] = tmp10 + tmp11;
+      output[4][i] = tmp10 - tmp11;
+
+      z1 = (tmp12 + tmp13) * 0.707106781;
+      output[2][i] = tmp13 + z1;
+      output[6][i] = tmp13 - z1;
+
+      tmp10 = tmp4 + tmp5;
+      tmp11 = tmp5 + tmp6;
+      tmp12 = tmp6 + tmp7;
+
+      z5 = (tmp10 - tmp12) * 0.382683433;
+      z2 = 0.541196100 * tmp10 + z5;
+      z4 = 1.306562965 * tmp12 + z5;
+      z3 = tmp11 * 0.707106781;
+
+      z11 = tmp7 + z3;
+      z13 = tmp7 - z3;
+
+      output[5][i] = z13 + z2;
+      output[3][i] = z13 - z2;
+      output[1][i] = z11 + z4;
+      output[7][i] = z11 - z4;
+    }
+
+    return output;
+  }
+
+  /*
+   * This method quantitizes data and rounds it to the nearest integer.
+   */
+  static int[] quantizeBlock(double inputData[][], double[] divisorsCode) {
+    int outputData[] = new int[NN];
+    for (int i = 0, index = 0; i < 8; i++)
+      for (int j = 0; j < 8; j++, index++)
+        // The second line results in significantly better compression.
+        outputData[index] = (int) (Math.round(inputData[i][j]
+            * divisorsCode[index]));
+    //                        outputData[index] = (int)(((inputData[i][j] * (((double[]) (Divisors[code]))[index])) + 16384.5) -16384);
+    return outputData;
+  }
+
+  /*
+   * This is the method for quantizing a block DCT'ed with forwardDCTExtreme
+   * This method quantitizes data and rounds it to the nearest integer.
+   */
+
+  /*
+
+    public double[][] forwardDCTExtreme(float input[][])
+    {
+      double output[][] = new double[N][N];
+      int v, u, x, y;
+      for (v = 0; v < 8; v++) {
+        for (u = 0; u < 8; u++) {
+          for (x = 0; x < 8; x++) {
+            for (y = 0; y < 8; y++) {
+              output[v][u] += input[x][y] * 
+                  Math.cos(((double)(2*x + 1)*(double)u*Math.PI)/16)*
+                  Math.cos(((double)(2*y + 1)*(double)v*Math.PI)/16);
+            }
+          }
+          output[v][u] *= (0.25)*((u == 0) ? (1.0/Math.sqrt(2)) : (double) 1.0)*((v == 0) ? (1.0/Math.sqrt(2)) : (double) 1.0);
+        }
+      }
+      return output;
+    }
+
+   */
+  /*
+    public int[] quantizeBlockExtreme(double inputData[][], int code)
+    {
+      int outputData[] = new int[NN];
+      int i, j;
+      int index;
+      index = 0;
+      for (i = 0; i < 8; i++) {
+        for (j = 0; j < 8; j++) {
+          outputData[index] = (int)(Math.round(inputData[i][j] / (((int[]) (quantum[code]))[index])));
+          index++;
+        }
+      }
+      
+      return outputData;
+    }
+  */
+}
+
+// This class was modified by James R. Weeks on 3/27/98.
+// It now incorporates Huffman table derivation as in the C jpeg library
+// from the IJG, Jpeg-6a.
+
+class Huffman {
+  private int bufferPutBits, bufferPutBuffer;
+  int imageHeight;
+  int imageWidth;
+  private int dc_matrix0[][];
+  private int ac_matrix0[][];
+  private int dc_matrix1[][];
+  private int ac_matrix1[][];
+  private int[][][] dc_matrix;
+  private int[][][] ac_matrix;
+  //private int code;
+  int numOfDCTables;
+  int numOfACTables;
+  final static int[] bitsDCluminance = { 0x00, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0,
+      0, 0, 0, 0, 0 };
+  final static int[] valDCluminance = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
+  final static int[] bitsDCchrominance = { 0x01, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1,
+      1, 0, 0, 0, 0, 0 };
+  final static int[] valDCchrominance = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
+  final static int[] bitsACluminance = { 0x10, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4,
+      4, 0, 0, 1, 0x7d };
+  final static int[] valACluminance = { 0x01, 0x02, 0x03, 0x00, 0x04, 0x11,
+      0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71,
+      0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52,
+      0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18,
+      0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
+      0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53,
+      0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+      0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,
+      0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
+      0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,
+      0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
+      0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
+      0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8,
+      0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa };
+  final static int[] bitsACchrominance = { 0x11, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5,
+      4, 4, 0, 1, 2, 0x77 };
+  final static int[] valACchrominance = { 0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
+      0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
+      0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33,
+      0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25,
+      0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36,
+      0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+      0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
+      0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+      0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
+      0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+      0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
+      0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
+      0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+      0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa };
+
+  /*
+   * jpegNaturalOrder[i] is the natural-order position of the i'th element
+   * of zigzag order.
+   */
+  final static int[] jpegNaturalOrder = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32,
+      25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14,
+      21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
+      58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, };
+
+  Huffman(int width, int height) {
+    initHuf();
+    imageWidth = width;
+    imageHeight = height;
+
+  }
+
+  /**
+   * HuffmanBlockEncoder run length encodes and Huffman encodes the quantized
+   * data.
+   * 
+   * @param out
+   * @param zigzag
+   * @param prec
+   * @param dcCode
+   * @param acCode
+   **/
+
+  void HuffmanBlockEncoder(OC out, int zigzag[], int prec,
+                           int dcCode, int acCode) {
+    int temp, temp2, nbits, k, r, i;
+
+    numOfDCTables = 2;
+    numOfACTables = 2;
+
+    int[][] matrixDC = dc_matrix[dcCode];
+    int[][] matrixAC = ac_matrix[acCode];
+
+    // The DC portion
+
+    temp = temp2 = zigzag[0] - prec;
+    if (temp < 0) {
+      temp = -temp;
+      temp2--;
+    }
+    nbits = 0;
+    while (temp != 0) {
+      nbits++;
+      temp >>= 1;
+    }
+    //        if (nbits > 11) nbits = 11;
+    bufferIt(out, matrixDC[nbits][0], matrixDC[nbits][1]);
+    // The arguments in bufferIt are code and size.
+    if (nbits != 0) {
+      bufferIt(out, temp2, nbits);
+    }
+
+    // The AC portion
+
+    r = 0;
+
+    for (k = 1; k < 64; k++) {
+      if ((temp = zigzag[jpegNaturalOrder[k]]) == 0) {
+        r++;
+      } else {
+        while (r > 15) {
+          bufferIt(out, matrixAC[0xF0][0], matrixAC[0xF0][1]);
+          r -= 16;
+        }
+        temp2 = temp;
+        if (temp < 0) {
+          temp = -temp;
+          temp2--;
+        }
+        nbits = 1;
+        while ((temp >>= 1) != 0) {
+          nbits++;
+        }
+        i = (r << 4) + nbits;
+        bufferIt(out, matrixAC[i][0], matrixAC[i][1]);
+        bufferIt(out, temp2, nbits);
+
+        r = 0;
+      }
+    }
+
+    if (r > 0) {
+      bufferIt(out, matrixAC[0][0], matrixAC[0][1]);
+    }
+
+  }
+
+  // Uses an integer long (32 bits) buffer to store the Huffman encoded bits
+  // and sends them to out by the byte.
+
+  void bufferIt(OC out, int code, int size) {
+    int putBuffer = code;
+    int putBits = bufferPutBits;
+
+    putBuffer &= (1 << size) - 1;
+    putBits += size;
+    putBuffer <<= 24 - putBits;
+    putBuffer |= bufferPutBuffer;
+
+    while (putBits >= 8) {
+      int c = ((putBuffer >> 16) & 0xFF);
+      out.writeByteAsInt(c);
+      if (c == 0xFF) {
+        out.writeByteAsInt(0);
+      }
+      putBuffer <<= 8;
+      putBits -= 8;
+    }
+    bufferPutBuffer = putBuffer;
+    bufferPutBits = putBits;
+
+  }
+
+  void flushBuffer(OC out) {
+    int putBuffer = bufferPutBuffer;
+    int putBits = bufferPutBits;
+    while (putBits >= 8) {
+      int c = ((putBuffer >> 16) & 0xFF);
+      out.writeByteAsInt(c);
+      if (c == 0xFF) {
+        out.writeByteAsInt(0);
+      }
+      putBuffer <<= 8;
+      putBits -= 8;
+    }
+    if (putBits > 0) {
+      int c = ((putBuffer >> 16) & 0xFF);
+      out.writeByteAsInt(c);
+    }
+  }
+
+  /*
+   * Initialisation of the Huffman codes for Luminance and Chrominance.
+   * This code results in the same tables created in the IJG Jpeg-6a
+   * library.
+   */
+
+  private void initHuf() {
+    dc_matrix0 = new int[12][2];
+    dc_matrix1 = new int[12][2];
+    ac_matrix0 = new int[255][2];
+    ac_matrix1 = new int[255][2];
+    dc_matrix = AU.newInt3(2, -1);
+    ac_matrix = AU.newInt3(2, -1);
+    int p, l, i, lastp, si, code;
+    int[] huffsize = new int[257];
+    int[] huffcode = new int[257];
+
+    /*
+     * init of the DC values for the chrominance
+     * [][0] is the code   [][1] is the number of bit
+     */
+
+    p = 0;
+    for (l = 1; l <= 16; l++) {
+      //      for (i = 1; i <= bitsDCchrominance[l]; i++)
+      for (i = bitsDCchrominance[l]; --i >= 0;) {
+        huffsize[p++] = l; //that's an "el", not a "one"
+      }
+    }
+    huffsize[p] = 0;
+    lastp = p;
+
+    code = 0;
+    si = huffsize[0];
+    p = 0;
+    while (huffsize[p] != 0) {
+      while (huffsize[p] == si) {
+        huffcode[p++] = code;
+        code++;
+      }
+      code <<= 1;
+      si++;
+    }
+
+    for (p = 0; p < lastp; p++) {
+      dc_matrix1[valDCchrominance[p]][0] = huffcode[p];
+      dc_matrix1[valDCchrominance[p]][1] = huffsize[p];
+    }
+
+    /*
+     * Init of the AC huffman code for the chrominance
+     * matrix [][][0] is the code & matrix[][][1] is the number of bit needed
+     */
+
+    p = 0;
+    for (l = 1; l <= 16; l++) {
+      for (i = bitsACchrominance[l]; --i >= 0;)
+      //      for (i = 1; i <= bitsACchrominance[l]; i++)
+      {
+        huffsize[p++] = l;
+      }
+    }
+    huffsize[p] = 0;
+    lastp = p;
+
+    code = 0;
+    si = huffsize[0];
+    p = 0;
+    while (huffsize[p] != 0) {
+      while (huffsize[p] == si) {
+        huffcode[p++] = code;
+        code++;
+      }
+      code <<= 1;
+      si++;
+    }
+
+    for (p = 0; p < lastp; p++) {
+      ac_matrix1[valACchrominance[p]][0] = huffcode[p];
+      ac_matrix1[valACchrominance[p]][1] = huffsize[p];
+    }
+
+    /*
+     * init of the DC values for the luminance
+     * [][0] is the code   [][1] is the number of bit
+     */
+    p = 0;
+    for (l = 1; l <= 16; l++) {
+      //      for (i = 1; i <= bitsDCluminance[l]; i++)
+      for (i = bitsDCluminance[l]; --i >= 0;) {
+        huffsize[p++] = l;
+      }
+    }
+    huffsize[p] = 0;
+    lastp = p;
+
+    code = 0;
+    si = huffsize[0];
+    p = 0;
+    while (huffsize[p] != 0) {
+      while (huffsize[p] == si) {
+        huffcode[p++] = code;
+        code++;
+      }
+      code <<= 1;
+      si++;
+    }
+
+    for (p = 0; p < lastp; p++) {
+      dc_matrix0[valDCluminance[p]][0] = huffcode[p];
+      dc_matrix0[valDCluminance[p]][1] = huffsize[p];
+    }
+
+    /*
+     * Init of the AC huffman code for luminance
+     * matrix [][][0] is the code & matrix[][][1] is the number of bit
+     */
+
+    p = 0;
+    for (l = 1; l <= 16; l++) {
+      //      for (i = 1; i <= bitsACluminance[l]; i++)
+      for (i = bitsACluminance[l]; --i >= 0;) {
+        huffsize[p++] = l;
+      }
+    }
+    huffsize[p] = 0;
+    lastp = p;
+
+    code = 0;
+    si = huffsize[0];
+    p = 0;
+    while (huffsize[p] != 0) {
+      while (huffsize[p] == si) {
+        huffcode[p++] = code;
+        code++;
+      }
+      code <<= 1;
+      si++;
+    }
+    for (int q = 0; q < lastp; q++) {
+      ac_matrix0[valACluminance[q]][0] = huffcode[q];
+      ac_matrix0[valACluminance[q]][1] = huffsize[q];
+    }
+
+    dc_matrix[0] = dc_matrix0;
+    dc_matrix[1] = dc_matrix1;
+    ac_matrix[0] = ac_matrix0;
+    ac_matrix[1] = ac_matrix1;
+  }
+
+}
+
+/*
+ * JpegInfo - Given an image, sets default information about it and divides
+ * it into its constituant components, downsizing those that need to be.
+ */
+
+class JpegObj {
+  String comment;
+  int imageHeight;
+  int imageWidth;
+  int blockWidth[];
+  int blockHeight[];
+
+  int precision = 8;
+  int numberOfComponents = 3;
+  float[][][] components;
+  int[] compID = { 1, 2, 3 };
+  int[] hsampFactor = { 1, 1, 1 };
+  int[] vsampFactor = { 1, 1, 1 };
+  int[] qtableNumber = { 0, 1, 1 };
+  int[] dctableNumber = { 0, 1, 1 };
+  int[] actableNumber = { 0, 1, 1 };
+  private boolean[] lastColumnIsDummy = { false, false, false };
+  private boolean[] lastRowIsDummy = { false, false, false };
+  int ss = 0;
+  int se = 63;
+  int ah = 0;
+  int al = 0;
+  private int compWidth[];
+  private int compHeight[];
+  private int maxHsampFactor;
+  private int maxVsampFactor;
+
+  public JpegObj() {
+    components = AU.newFloat3(numberOfComponents, -1);
+    compWidth = new int[numberOfComponents];
+    compHeight = new int[numberOfComponents];
+    blockWidth = new int[numberOfComponents];
+    blockHeight = new int[numberOfComponents];
+  }
+
+  /*
+   * This method creates and fills three arrays, Y, Cb, and Cr using the
+   * input image.
+   */
+
+  void getYCCArray(int[] pixels) {
+    // In order to minimize the chance that grabPixels will throw an exception
+    // it may be necessary to grab some pixels every few scanlines and process
+    // those before going for more.  The time expense may be prohibitive.
+    // However, for a situation where memory overhead is a concern, this may be
+    // the only choice.
+    maxHsampFactor = 1;
+    maxVsampFactor = 1;
+    for (int y = 0; y < numberOfComponents; y++) {
+      maxHsampFactor = Math.max(maxHsampFactor, hsampFactor[y]);
+      maxVsampFactor = Math.max(maxVsampFactor, vsampFactor[y]);
+    }
+    for (int y = 0; y < numberOfComponents; y++) {
+      compWidth[y] = (((imageWidth % 8 != 0) ? ((int) Math
+          .ceil(imageWidth / 8.0)) * 8 : imageWidth) / maxHsampFactor)
+          * hsampFactor[y];
+      if (compWidth[y] != ((imageWidth / maxHsampFactor) * hsampFactor[y])) {
+        lastColumnIsDummy[y] = true;
+      }
+      // results in a multiple of 8 for compWidth
+      // this will make the rest of the program fail for the unlikely
+      // event that someone tries to compress an 16 x 16 pixel image
+      // which would of course be worse than pointless
+      blockWidth[y] = (int) Math.ceil(compWidth[y] / 8.0);
+      compHeight[y] = (((imageHeight % 8 != 0) ? ((int) Math
+          .ceil(imageHeight / 8.0)) * 8 : imageHeight) / maxVsampFactor)
+          * vsampFactor[y];
+      if (compHeight[y] != ((imageHeight / maxVsampFactor) * vsampFactor[y])) {
+        lastRowIsDummy[y] = true;
+      }
+      blockHeight[y] = (int) Math.ceil(compHeight[y] / 8.0);
+    }
+    float Y[][] = new float[compHeight[0]][compWidth[0]];
+    float Cr1[][] = new float[compHeight[0]][compWidth[0]];
+    float Cb1[][] = new float[compHeight[0]][compWidth[0]];
+    //float Cb2[][] = new float[compHeight[1]][compWidth[1]];
+    //float Cr2[][] = new float[compHeight[2]][compWidth[2]];
+    for (int pt = 0, y = 0; y < imageHeight; ++y) {
+      for (int x = 0; x < imageWidth; ++x, pt++) {
+        int p = pixels[pt];
+        int r = ((p >> 16) & 0xff);
+        int g = ((p >> 8) & 0xff);
+        int b = (p & 0xff);
+        // The following three lines are a more correct color conversion but
+        // the current conversion technique is sufficient and results in a higher
+        // compression rate.
+        // Y[y][x] = 16 + (float)(0.8588*(0.299 * (float)r + 0.587 * (float)g + 0.114 * (float)b ));
+        // Cb1[y][x] = 128 + (float)(0.8784*(-0.16874 * (float)r - 0.33126 * (float)g + 0.5 * (float)b));
+        // Cr1[y][x] = 128 + (float)(0.8784*(0.5 * (float)r - 0.41869 * (float)g - 0.08131 * (float)b));
+        Y[y][x] = (float) ((0.299 * r + 0.587 * g + 0.114 * b));
+        Cb1[y][x] = 128 + (float) ((-0.16874 * r - 0.33126 * g + 0.5 * b));
+        Cr1[y][x] = 128 + (float) ((0.5 * r - 0.41869 * g - 0.08131 * b));
+      }
+    }
+
+    // Need a way to set the H and V sample factors before allowing downsampling.
+    // For now (04/04/98) downsampling must be hard coded.
+    // Until a better downsampler is implemented, this will not be done.
+    // Downsampling is currently supported.  The downsampling method here
+    // is a simple box filter.
+
+    components[0] = Y;
+    //        Cb2 = DownSample(Cb1, 1);
+    components[1] = Cb1;
+    //        Cr2 = DownSample(Cr1, 2);
+    components[2] = Cr1;
+  }
+  /*  
+    float[][] DownSample(float[][] C, int comp)
+    {
+      int inrow, incol;
+      int outrow, outcol;
+      float output[][];
+      int bias;
+      inrow = 0;
+      incol = 0;
+      int cHeight = compHeight[comp];
+      int cWidth = compWidth[comp];
+      output = new float[cHeight][cWidth];
+      
+      for (outrow = 0; outrow < cHeight; outrow++) {
+        bias = 1;
+        for (outcol = 0; outcol < cWidth; outcol++) {
+          output[outrow][outcol] = (C[inrow][incol++] + C[inrow++][incol--] 
+                   + C[inrow][incol++] + C[inrow--][incol++] + bias)/(float)4.0;
+          bias ^= 3;
+        }
+        inrow += 2;
+        incol = 0;
+      }
+      return output;
+    }
+  */
+
+}
diff --git a/src/javajs/img/PdfEncoder.java b/src/javajs/img/PdfEncoder.java
new file mode 100644 (file)
index 0000000..7dfd4b8
--- /dev/null
@@ -0,0 +1,108 @@
+/* $RCSfile$
+ * $Author: hansonr $
+ * $Date: 2009-06-30 18:58:33 -0500 (Tue, 30 Jun 2009) $
+ * $Revision: 11158 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2002-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.img;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import javajs.export.PDFCreator;
+
+/**
+ * A relatively primitive PDF generator that just makes a document with an image
+ * in it.
+ * 
+ */
+public class PdfEncoder extends ImageEncoder {
+
+  private boolean isLandscape;
+  private PDFCreator pdf;
+  private String comment;
+
+  public PdfEncoder() {
+    // for Class.forName  
+  }
+
+  @Override
+  protected void setParams(Map<String, Object> params) {
+       isLandscape = (quality > 1);
+    comment = "Jmol " + (String) params.get("comment");
+  }
+
+  @Override
+  protected void generate() throws Exception {
+    pdf = new PDFCreator();
+    int pageWidth = 8 * 72;
+    int pageHeight = 11 * 72;
+    pdf.setOutputStream(out);
+    pdf.newDocument(pageWidth, pageHeight, isLandscape); // A4 or Letter
+    addMyImage(pageWidth, pageHeight);
+    Map<String, String> ht = new Hashtable<String, String>();
+    if (comment != null)
+      ht.put("Producer", comment);
+    ht.put("Author", "JMol");
+    ht.put("CreationDate", date);
+    pdf.addInfo(ht);
+    pdf.closeDocument();
+  }
+
+  
+  /**
+   * centered on the page
+   * 
+   * @param pageWidth
+   * @param pageHeight
+   */
+  private void addMyImage(int pageWidth, int pageHeight) {
+    pdf.addImageResource("img1", width, height, pixels, true);
+    int w = (isLandscape ? pageHeight : pageWidth);
+    int h = (isLandscape ? pageWidth : pageHeight);
+    int iw = width;
+    int ih = height;
+    if (iw > 0.9 * w) {
+      ih = (int) (ih * 0.9 * w / iw);
+      iw = (int) (w * 0.9);
+    }
+    if (ih > 0.9 * h) {
+      iw = (int) (iw * 0.9 * h / ih);
+      ih = (int) (h * 0.9);
+    }
+    int x = 0;
+    int y = 0;
+    int x1 = iw;
+    int y1 = ih;
+    if (w > iw) {
+      x = (w - iw) / 2;
+      x1 = iw + x;
+    }
+    if (h > ih) {
+      y = (h - ih) / 2;
+      y1 = ih + y;
+    }
+    pdf.drawImage("img1", x, y, x1, y1, 0, 0, width, height);
+  }
+
+}
diff --git a/src/javajs/img/PngEncoder.java b/src/javajs/img/PngEncoder.java
new file mode 100644 (file)
index 0000000..2dc8485
--- /dev/null
@@ -0,0 +1,480 @@
+/* $RCSfile$
+ * $Author: nicove $
+ * $Date: 2007-03-30 12:26:16 -0500 (Fri, 30 Mar 2007) $
+ * $Revision: 7275 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2002-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.img;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+
+
+/**
+ * 
+ * Modified by Bob Hanson hansonr@stolaf.edu to be a subclass of ImageEncoder
+ * and to use javajs.util.OutputChannel instead of just returning bytes. Also includes: 
+ *  
+ * -- JavaScript-compatible image processing
+ *  
+ * -- transparent background option
+ *  
+ * -- more efficient calculation of needs for pngBytes 
+ * 
+ * -- option to use pre-created PNGJ image data (3/19/14; Jmol 14.1.12)
+ * 
+ * -- PNGJ format:
+ * 
+ * // IHDR chunk 
+ * 
+ * // tEXt chunk "Jmol type - <PNG0|PNGJ|PNGT><0000000pt>+<000000len>" 
+ * 
+ * // tEXt chunk "Software - Jmol <version>"
+ * 
+ * // tEXt chunk "Creation Time - <date>"
+ * 
+ * // tRNS chunk transparent color, if desired
+ *
+ * // IDAT chunk (image data)
+ * 
+ * // IEND chunk 
+ * 
+ * // [JMOL ZIP FILE APPENDIX]
+ * 
+ * Original Comment:
+ * 
+ * PngEncoder takes a Java Image object and creates a byte string which can be
+ * saved as a PNG file. The Image is presumed to use the DirectColorModel.
+ * 
+ * Thanks to Jay Denny at KeyPoint Software http://www.keypoint.com/ who let me
+ * develop this code on company time.
+ * 
+ * You may contact me with (probably very-much-needed) improvements, comments,
+ * and bug fixes at:
+ * 
+ * david@catcode.com
+ * 
+ * @author J. David Eisenberg
+ * @author http://catcode.com/pngencoder/
+ * @author Christian Ribeaud (christian.ribeaud@genedata.com)
+ * @author Bob Hanson (hansonr@stolaf.edu)
+ * 
+ * @version 1.4, 31 March 2000
+ */
+public class PngEncoder extends CRCEncoder {
+
+  /** Constants for filters */
+  public static final int FILTER_NONE = 0;
+  public static final int FILTER_SUB = 1;
+  public static final int FILTER_UP = 2;
+  public static final int FILTER_LAST = 2;
+  
+  private static final int PT_FIRST_TAG = 37;
+
+  private boolean encodeAlpha;
+  private int filter = FILTER_NONE;
+  private int bytesPerPixel;
+  private int compressionLevel;
+  private String type;
+  private Integer transparentColor;
+
+  private byte[] appData;
+  private String appPrefix;
+  private String comment;
+  private byte[] bytes;
+
+  
+  public PngEncoder() {
+    super();
+  }
+
+  @Override
+  protected void setParams(Map<String, Object> params) {
+    if (quality < 0)
+      quality = (params.containsKey("qualityPNG") ? ((Integer) params
+          .get("qualityPNG")).intValue() : 2);
+    if (quality > 9)
+      quality = 9;
+    encodeAlpha = false;
+    filter = FILTER_NONE;
+    compressionLevel = quality;
+    transparentColor = (Integer) params.get("transparentColor");
+    comment = (String) params.get("comment");
+    type = (params.get("type") + "0000").substring(0, 4);
+    bytes = (byte[]) params.get("pngImgData");
+    appData = (byte[]) params.get("pngAppData");
+    appPrefix = (String) params.get("pngAppPrefix");
+  }
+
+  
+
+  @Override
+  protected void generate() throws IOException {
+    if (bytes == null) {
+      if (!pngEncode()) {
+        out.cancel();
+        return;
+      }
+      bytes = getBytes();
+    } else {
+      dataLen = bytes.length;
+    }
+    int len = dataLen;
+    if (appData != null) {
+      setJmolTypeText(appPrefix, bytes, len, appData.length,
+          type);
+      out.write(bytes, 0, len);
+      len = (bytes = appData).length;
+    }
+    out.write(bytes, 0, len);
+  }
+
+
+  /**
+   * Creates an array of bytes that is the PNG equivalent of the current image,
+   * specifying whether to encode alpha or not.
+   * 
+   * @return        true if successful
+   * 
+   */
+  private boolean pngEncode() {
+
+    byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 };
+
+    writeBytes(pngIdBytes);
+    //hdrPos = bytePos;
+    writeHeader();
+    writeText(getApplicationText(appPrefix, type, 0, 0));
+
+    writeText("Software\0" + comment);
+    writeText("Creation Time\0" + date);
+
+    if (!encodeAlpha && transparentColor != null)
+      writeTransparentColor(transparentColor.intValue());
+    //dataPos = bytePos;
+    return writeImageData();
+  }
+
+  /**
+   * Fill in the Jmol type text area with number of bytes of PNG data and number
+   * of bytes of Jmol state data and fix checksum.
+   * 
+   * If we do not do this, then the checksum will be wrong, and Jmol and some
+   * other programs may not be able to read the PNG image.
+   * 
+   * This was corrected for Jmol 12.3.30. Between 12.3.7 and 12.3.29, PNG files
+   * created by Jmol have incorrect checksums.
+   * 
+   * @param prefix 
+   * 
+   * @param b
+   * @param nPNG
+   * @param nState
+   * @param type
+   */
+  private static void setJmolTypeText(String prefix, byte[] b, int nPNG, int nState, String type) {
+    String s = "tEXt" + getApplicationText(prefix, type, nPNG, nState);
+    CRCEncoder encoder = new PngEncoder();
+    byte[] test = s.substring(0, 4 + prefix.length()).getBytes();
+    for (int i = test.length; -- i >= 0;) 
+      if (b[i + PT_FIRST_TAG] != test[i]) {
+        System.out.println("image is not of the right form; appending data, but not adding tEXt tag.");
+        return;
+      }
+    encoder.setData(b, PT_FIRST_TAG);
+    encoder.writeString(s);
+    encoder.writeCRC();
+  }
+
+  /**
+   * Generate the PNGJ directory identifier:
+   * 
+   *    xxxxxxxxx\0ttttiiiiiiiii+ddddddddd
+   *    
+   * where 
+   * 
+   * xxxxxxxxx is a unique 9-character software identifier
+   * tttt is a four-byte software-specific type indicator (PNG0, PNGJ, PNGT, etc.)
+   * iiiiiiiii is the file pointer to the start of app data
+   * ddddddddd is the length of the app data
+   * 
+   * @param prefix up to 9 characters to allow software to recognize itself 
+   * @param type PNGx, where x is J or T for Jmol; original type "PNG" is now "PNG0" 
+   * @param nPNG
+   * @param nData
+   * @return
+   */
+       private static String getApplicationText(String prefix, String type,
+                       int nPNG, int nData) {
+               String sPNG = "000000000" + nPNG;
+               sPNG = sPNG.substring(sPNG.length() - 9);
+               String sData = "000000000" + nData;
+               sData = sData.substring(sData.length() - 9);
+               if (prefix == null)
+                       prefix = "#SwingJS.";                   
+               if (prefix.length() < 9)
+                       prefix = (prefix + ".........");
+               if (prefix.length() > 9)
+                       prefix = prefix.substring(0, 9);
+               return prefix + "\0" + type + sPNG + "+" + sData;
+       }
+
+  //  /**
+  //   * Set the filter to use
+  //   *
+  //   * @param whichFilter from constant list
+  //   */
+  //  public void setFilter(int whichFilter) {
+  //    this.filter = (whichFilter <= FILTER_LAST ? whichFilter : FILTER_NONE);
+  //  }
+
+  //  /**
+  //   * Retrieve filtering scheme
+  //   *
+  //   * @return int (see constant list)
+  //   */
+  //  public int getFilter() {
+  //    return filter;
+  //  }
+
+  //  /**
+  //   * Set the compression level to use
+  //   *
+  //   * @param level 0 through 9
+  //   */
+  //  public void setCompressionLevel(int level) {
+  //    if ((level >= 0) && (level <= 9)) {
+  //      this.compressionLevel = level;
+  //    }
+  //  }
+
+  //  /**
+  //   * Retrieve compression level
+  //   *
+  //   * @return int in range 0-9
+  //   */
+  //  public int getCompressionLevel() {
+  //    return compressionLevel;
+  //  }
+
+  /**
+   * Write a PNG "IHDR" chunk into the pngBytes array.
+   */
+  private void writeHeader() {
+
+    writeInt4(13);
+    startPos = bytePos;
+    writeString("IHDR");
+    writeInt4(width);
+    writeInt4(height);
+    writeByte(8); // bit depth
+    writeByte(encodeAlpha ? 6 : 2); // color type or direct model
+    writeByte(0); // compression method
+    writeByte(0); // filter method
+    writeByte(0); // no interlace
+    writeCRC();
+  }
+
+  private void writeText(String msg) {
+    writeInt4(msg.length());
+    startPos = bytePos;
+    writeString("tEXt" + msg);
+    writeCRC();
+  }
+
+  /**
+   * Write a PNG "tRNS" chunk into the pngBytes array.
+   * 
+   * @param icolor
+   */
+  private void writeTransparentColor(int icolor) {
+
+    writeInt4(6);
+    startPos = bytePos;
+    writeString("tRNS");
+    writeInt2((icolor >> 16) & 0xFF);
+    writeInt2((icolor >> 8) & 0xFF);
+    writeInt2(icolor & 0xFF);
+    writeCRC();
+  }
+
+  private byte[] scanLines; // the scan lines to be compressed
+  private int byteWidth; // width * bytesPerPixel
+
+  //private int hdrPos, dataPos, endPos;
+  //private byte[] priorRow;
+  //private byte[] leftBytes;
+
+
+  /**
+   * Write the image data into the pngBytes array. This will write one or more
+   * PNG "IDAT" chunks. In order to conserve memory, this method grabs as many
+   * rows as will fit into 32K bytes, or the whole image; whichever is less.
+   * 
+   * 
+   * @return true if no errors; false if error grabbing pixels
+   */
+  private boolean writeImageData() {
+
+    bytesPerPixel = (encodeAlpha ? 4 : 3);
+    byteWidth = width * bytesPerPixel;
+
+    int scanWidth = byteWidth + 1; // the added 1 is for the filter byte
+
+    //boolean doFilter = (filter != FILTER_NONE);
+
+    int rowsLeft = height; // number of rows remaining to write
+    //int startRow = 0; // starting row to process this time through
+    int nRows; // how many rows to grab at a time
+
+    int scanPos; // where we are in the scan lines
+
+    Deflater deflater = new Deflater(compressionLevel);
+    ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024);
+
+    DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes,
+        deflater);
+
+    int pt = 0; // overall image byte pointer
+    
+    // Jmol note: The entire image has been stored in pixels[] already
+    
+    try {
+      while (rowsLeft > 0) {
+        nRows = Math.max(1, Math.min(32767 / scanWidth, rowsLeft));
+        scanLines = new byte[scanWidth * nRows];
+        //        if (doFilter)
+        //          switch (filter) {
+        //          case FILTER_SUB:
+        //            leftBytes = new byte[16];
+        //            break;
+        //          case FILTER_UP:
+        //            priorRow = new byte[scanWidth - 1];
+        //            break;
+        //          }
+        int nPixels = width * nRows;
+        scanPos = 0;
+        //startPos = 1;
+        for (int i = 0; i < nPixels; i++, pt++) {
+          if (i % width == 0) {
+            scanLines[scanPos++] = (byte) filter;
+            //startPos = scanPos;
+          }
+          scanLines[scanPos++] = (byte) ((pixels[pt] >> 16) & 0xff);
+          scanLines[scanPos++] = (byte) ((pixels[pt] >> 8) & 0xff);
+          scanLines[scanPos++] = (byte) ((pixels[pt]) & 0xff);
+          if (encodeAlpha) {
+            scanLines[scanPos++] = (byte) ((pixels[pt] >> 24) & 0xff);
+          }
+          //          if (doFilter && i % width == width - 1) {
+          //            switch (filter) {
+          //            case FILTER_SUB:
+          //              filterSub();
+          //              break;
+          //            case FILTER_UP:
+          //              filterUp();
+          //              break;
+          //            }
+          //          }
+        }
+
+        /*
+         * Write these lines to the output area
+         */
+        compBytes.write(scanLines, 0, scanPos);
+
+        //startRow += nRows;
+        rowsLeft -= nRows;
+      }
+      compBytes.close();
+
+      /*
+       * Write the compressed bytes
+       */
+      byte[] compressedLines = outBytes.toByteArray();
+      writeInt4(compressedLines.length);
+      startPos = bytePos;
+      writeString("IDAT");
+      writeBytes(compressedLines);
+      writeCRC();
+      writeEnd();
+      deflater.finish();
+      return true;
+    } catch (IOException e) {
+      System.err.println(e.toString());
+      return false;
+    }
+  }
+
+  /**
+   * Write a PNG "IEND" chunk into the pngBytes array.
+   */
+  private void writeEnd() {
+    writeInt4(0);
+    startPos = bytePos;
+    writeString("IEND");
+    writeCRC();
+  }
+
+  ///**
+  //* Perform "sub" filtering on the given row.
+  //* Uses temporary array leftBytes to store the original values
+  //* of the previous pixels.  The array is 16 bytes long, which
+  //* will easily hold two-byte samples plus two-byte alpha.
+  //*
+  //*/
+  //private void filterSub() {
+  // int offset = bytesPerPixel;
+  // int actualStart = startPos + offset;
+  // int leftInsert = offset;
+  // int leftExtract = 0;
+  // //byte current_byte;
+  //
+  // for (int i = actualStart; i < startPos + byteWidth; i++) {
+  //   leftBytes[leftInsert] = scanLines[i];
+  //   scanLines[i] = (byte) ((scanLines[i] - leftBytes[leftExtract]) % 256);
+  //   leftInsert = (leftInsert + 1) % 0x0f;
+  //   leftExtract = (leftExtract + 1) % 0x0f;
+  // }
+  //}
+  //
+  ///**
+  //* Perform "up" filtering on the given row. Side effect: refills the prior row
+  //* with current row
+  //* 
+  //*/
+  //private void filterUp() {
+  // int nBytes = width * bytesPerPixel;
+  // for (int i = 0; i < nBytes; i++) {
+  //   int pt = startPos + i;
+  //   byte b = scanLines[pt];
+  //   scanLines[pt] = (byte) ((scanLines[pt] - priorRow[i]) % 256);
+  //   priorRow[i] = b;
+  // }
+  //}
+
+}
diff --git a/src/javajs/img/PpmEncoder.java b/src/javajs/img/PpmEncoder.java
new file mode 100644 (file)
index 0000000..4bdc980
--- /dev/null
@@ -0,0 +1,60 @@
+// PpmEncoder - write out an image as a PPM
+//
+// Copyright (C)1996,1998 by Jef Poskanzer <jef@mail.acme.com>. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+//    notice, this list of conditions and the following disclaimer in the
+//    documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+// SUCH DAMAGE.
+//
+// Visit the ACME Labs Java page for up-to-date versions of this and other
+// fine Java utilities: http://www.acme.com/java/
+
+package javajs.img;
+
+import java.util.Map;
+
+
+/**
+ * see http://netpbm.sourceforge.net/doc/ppm.html
+ */
+public class PpmEncoder extends ImageEncoder {
+
+  @Override
+  protected void setParams(Map<String, Object> params) {
+    // no params
+  }
+
+  @Override
+  protected void generate() {
+    putString("P6\n");
+    putString(width + " " + height + "\n");
+    putString("255\n");
+    byte[] ppmPixels = new byte[width * 3];
+    for (int pt = 0, row = 0; row < height; ++row) {
+      for (int col = 0, j = 0; col < width; ++col, pt++) {
+        int p = pixels[pt];
+        ppmPixels[j++] = (byte) ((p >> 16) & 0xff);
+        ppmPixels[j++] = (byte) ((p >> 8) & 0xff);
+        ppmPixels[j++] = (byte) (p & 0xff);
+      }
+      out.write(ppmPixels, 0, ppmPixels.length);
+    }
+  }
+}
diff --git a/src/javajs/util/A4.java b/src/javajs/util/A4.java
new file mode 100644 (file)
index 0000000..d20b6c2
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+import java.io.Serializable;
+
+import javajs.api.JSONEncodable;
+import javajs.util.T3;
+
+
+
+/**
+ * A 4 element axis angle represented by single precision floating point
+ * x,y,z,angle components. An axis angle is a rotation of angle (radians) about
+ * the vector (x,y,z).
+ * 
+ * @version specification 1.1, implementation $Revision: 1.9 $, $Date:
+ *          2006/07/28 17:01:32 $
+ * @author Kenji hiranabe
+ * 
+ * additions by Bob Hanson hansonr@stolaf.edu 9/30/2012
+ * for unique constructor and method names
+ * for the optimization of compiled JavaScript using Java2Script
+ */
+public class A4 implements JSONEncodable, Serializable {
+
+  /*
+   * I assumed that the length of the axis vector is not significant.
+   */
+
+  /**
+   * The x coordinate.
+   */
+  public float x;
+
+  /**
+   * The y coordinate.
+   */
+  public float y;
+
+  /**
+   * The z coordinate.
+   */
+  public float z;
+
+  /**
+   * The angle.
+   */
+  public float angle;
+
+  /**
+   * Constructs and initializes a AxisAngle4f to (0,0,1,0).
+   */
+  public A4() {
+    z = 1.0f;
+  }
+
+  /**
+   * Constructs and initializes an AxisAngle4f from the specified x, y, z, and
+   * angle.
+   * 
+   * @param x
+   *        the x coordinate
+   * @param y
+   *        the y coordinate
+   * @param z
+   *        the z coordinate
+   * @param angle
+   *        the angle.
+   * @return a
+   */
+  public static A4 new4(float x, float y, float z, float angle) {
+    A4 a = new A4();
+    a.set4(x, y, z, angle);
+    return a;
+  }
+
+  /**
+   * Constructs and initializes a AxisAngle4f from the specified AxisAngle4f.
+   * 
+   * @param a1
+   *        the AxisAngle4f containing the initialization x y z angle data
+   * @return a
+   */
+  public static A4 newAA(A4 a1) {
+    A4 a = new A4();
+    a.set4(a1.x, a1.y, a1.z, a1.angle);
+    return a;
+  }
+
+  /**
+   * Constructs and initializes an AxisAngle4f from the specified axis and
+   * angle.
+   * 
+   * @param axis
+   *        the axis
+   * @param angle
+   *        the angle
+   * @return a
+   */
+  public static A4 newVA(V3 axis, float angle) {
+    A4 a = new A4();
+    a.setVA(axis, angle);
+    return a;
+  }
+
+  /**
+   * Sets the value of this AxisAngle4f to the specified axis and angle.
+   * 
+   * @param axis
+   *        the axis
+   * @param angle
+   *        the angle
+   * @since Java 3D 1.2
+   */
+  public final void setVA(V3 axis, float angle) {
+    x = axis.x;
+    y = axis.y;
+    z = axis.z;
+    this.angle = angle;
+  }
+
+  /**
+   * Sets the value of this axis angle to the specified x,y,z,angle.
+   * 
+   * @param x
+   *        the x coordinate
+   * @param y
+   *        the y coordinate
+   * @param z
+   *        the z coordinate
+   * @param angle
+   *        the angle
+   */
+  public final void set4(float x, float y, float z, float angle) {
+    this.x = x;
+    this.y = y;
+    this.z = z;
+    this.angle = angle;
+  }
+
+  /**
+   * Sets the value of this axis angle to the value of axis angle t1.
+   * 
+   * @param a
+   *        the axis angle to be copied
+   */
+  public final void setAA(A4 a) {
+    x = a.x;
+    y = a.y;
+    z = a.z;
+    angle = a.angle;
+  }
+
+
+  /**
+   * Sets the value of this axis-angle to the rotational component of the passed
+   * matrix.
+   * 
+   * @param m1
+   *        the matrix3f
+   */
+  public final void setM(M3 m1) {
+    setFromMat(m1.m00, m1.m01, m1.m02, m1.m10, m1.m11, m1.m12, m1.m20, m1.m21,
+        m1.m22);
+  }
+
+  // helper method
+  private void setFromMat(double m00, double m01, double m02, double m10,
+                          double m11, double m12, double m20, double m21,
+                          double m22) {
+    // assuming M is normalized.
+
+    double cos = (m00 + m11 + m22 - 1.0) * 0.5;
+    x = (float) (m21 - m12);
+    y = (float) (m02 - m20);
+    z = (float) (m10 - m01);
+    double sin = 0.5 * Math.sqrt(x * x + y * y + z * z);
+    if (sin == 0 && cos == 1) {
+      x = y = 0;
+      z = 1;
+      angle = 0;
+    } else {
+      angle = (float) Math.atan2(sin, cos);
+    }
+
+    // no need to normalize
+    // x /= n;
+    // y /= n;
+    // z /= n;
+  }
+
+  /**
+   * Returns a hash number based on the data values in this object. Two
+   * different AxisAngle4f objects with identical data values (ie, returns true
+   * for equals(AxisAngle4f) ) will return the same hash number. Two vectors
+   * with different data members may return the same hash value, although this
+   * is not likely.
+   */
+  @Override
+  public int hashCode() {
+    return T3.floatToIntBits(x) ^ T3.floatToIntBits(y)
+        ^ T3.floatToIntBits(z) ^ T3.floatToIntBits(angle);
+  }
+
+  /**
+   * Returns true if the Object o is of type AxisAngle4f and all of the data
+   * members of o1 are equal to the corresponding data members in this
+   * AxisAngle4f.
+   * 
+   * @param o
+   *        the object with which the comparison is made.
+   * @return T/F
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof A4))
+      return false;
+    A4 a1 = (A4) o;
+    return x == a1.x && y == a1.y && z == a1.z && angle == a1.angle;
+  }
+
+  /**
+   * Returns a string that contains the values of this AxisAngle4f. The form is
+   * (x,y,z,angle).
+   * 
+   * @return the String representation
+   */
+  @Override
+  public String toString() {
+    return "(" + x + ", " + y + ", " + z + ", " + angle + ")";
+  }
+
+  @Override
+  public String toJSON() {
+    return "[" + x + "," + y + "," + z + "," + (float) (angle * 180.0 / Math.PI) + "]";
+  }
+}
diff --git a/src/javajs/util/AU.java b/src/javajs/util/AU.java
new file mode 100644 (file)
index 0000000..f873116
--- /dev/null
@@ -0,0 +1,551 @@
+/* $RCSfile$
+ * $Author: egonw $
+ * $Date: 2005-11-10 09:52:44 -0600 (Thu, 10 Nov 2005) $
+ * $Revision: 4255 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2003-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.util;
+
+// 4/23/15 BH getComponentType fix
+
+import java.lang.reflect.Array;
+
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.Map;
+
+
+final public class AU {
+
+  /**
+   * Very important that this not be used with Int32Array or Float32Array,
+   * because it is not initialize to all zeros in MSIE 9. 
+   * 
+   * @param array
+   * @param minimumLength
+   * @return array
+   */
+  public static Object ensureLength(Object array, int minimumLength) {
+    return (array != null && getLength(array) >= minimumLength ? array 
+        : arrayCopyObject(array, minimumLength));
+  }
+
+  public static String[] ensureLengthS(String[] array, int minimumLength) {
+    return (array != null && array.length >= minimumLength ? array
+        : arrayCopyS(array, minimumLength));
+  }
+
+  public static float[] ensureLengthA(float[] array, int minimumLength) {
+   return (array != null && array.length >= minimumLength ? array: arrayCopyF(array, minimumLength));
+  }
+
+  public static int[] ensureLengthI(int[] array, int minimumLength) {
+    return (array != null && array.length >= minimumLength ? array : arrayCopyI(array, minimumLength));
+  }
+
+  public static short[] ensureLengthShort(short[] array, int minimumLength) {
+    return (array != null && array.length >= minimumLength ? array : arrayCopyShort(array, minimumLength));
+  }
+
+  public static byte[] ensureLengthByte(byte[] array, int minimumLength) {
+    return (array != null && array.length >= minimumLength ? array : arrayCopyByte(array, minimumLength));
+  }
+
+  /**
+   * Very important that this not be used with Int32Array or Float32Array,
+   * because it is not initialized to all zeros in MSIE 9.
+   * 
+   * @param array
+   * @return array
+   */
+  public static Object doubleLength(Object array) {
+    return arrayCopyObject(array, (array == null ? 16 : 2 * getLength(array)));
+  }
+
+  public static String[] doubleLengthS(String[] array) {
+    return arrayCopyS(array, (array == null ? 16 : 2 * array.length));
+  }
+
+  public static float[] doubleLengthF(float[] array) {
+    return arrayCopyF(array, (array == null ? 16 : 2 * array.length));
+  }
+
+  public static int[] doubleLengthI(int[] array) {
+    return arrayCopyI(array, (array == null ? 16 : 2 * array.length));
+  }
+
+  public static short[] doubleLengthShort(short[] array) {
+    return arrayCopyShort(array, (array == null ? 16 : 2 * array.length));
+  }
+
+  public static byte[] doubleLengthByte(byte[] array) {
+    return arrayCopyByte(array, (array == null ? 16 : 2 * array.length));
+  }
+
+  public static boolean[] doubleLengthBool(boolean[] array) {
+    return arrayCopyBool(array, (array == null ? 16 : 2 * array.length));
+  }
+
+  public static Object deleteElements(Object array, int firstElement,
+                                     int nElements) {
+    if (nElements == 0 || array == null)
+      return array;
+    int oldLength = getLength(array);
+    if (firstElement >= oldLength)
+      return array;
+    int n = oldLength - (firstElement + nElements);
+    if (n < 0)
+      n = 0;
+    Object t = newInstanceO(array, firstElement + n);
+    if (firstElement > 0)
+      System.arraycopy(array, 0, t, 0, firstElement);
+    if (n > 0)
+      System.arraycopy(array, firstElement + nElements, t, firstElement, n);
+    return t;
+  }
+
+  /**
+   * note -- cannot copy if array is null! does not copy if length is unchanged
+   * 
+   * @param array
+   * @param newLength
+   * @return array
+   */
+  public static Object arrayCopyObject(Object array, int newLength) {  
+    int oldLength = (array == null ? -1 : getLength(array));
+    if (newLength < 0) newLength = oldLength;
+    if (newLength == oldLength)
+     return array;
+    /**
+     * @j2sNative
+     * 
+     *     if (newLength < oldLength) return Clazz.array(-1, array, 0, newLength);
+     */
+    {}
+    Object t = newInstanceO(array, newLength);
+    if (oldLength > 0)
+      System.arraycopy(array, 0, t, 0, oldLength < newLength ? oldLength
+        : newLength);
+    return t;
+
+  }
+
+  /**
+   * Very important that this not be used with Int32Array or Float32Array,
+   * because those need to be initialized to all zeros in MSIE 9, and
+   * MSIE 9 cannot distinguish Int32Array or Float32Array from Array.
+   * 
+   * @param array
+   * @param n
+   * @return array
+   */
+  private static Object newInstanceO(Object array, int n) {
+    return Array.newInstance(array.getClass().getComponentType(), n);
+  }
+
+  public static int getLength(Object array) {
+    /**
+     * @j2sNative
+     * 
+     *  return array.length
+     *   
+     */
+    {
+      return Array.getLength(array);
+    }
+  }
+
+  public static String[] arrayCopyS(String[] array, int newLength) {
+    int oldLength = (array == null ? -1 : array.length);
+    if (newLength < 0) newLength = oldLength;
+    /**
+     * @j2sNative
+     * 
+     *     if (newLength < oldLength) return Clazz.array(-1, array, 0, newLength);
+     */
+    {}
+    String[] t = new String[newLength];
+    if (array != null) {
+      System.arraycopy(array, 0, t, 0, oldLength < newLength ? oldLength
+          : newLength);
+    }
+    return t;
+  }
+
+  public static int[][] arrayCopyII(int[][] array, int newLength) {
+    int[][] t = newInt2(newLength);
+    if (array != null) {
+      int oldLength = array.length;
+      System.arraycopy(array, 0, t, 0, oldLength < newLength ? oldLength
+          : newLength);
+    }
+    return t;
+  }
+
+  public static T3[] arrayCopyPt(T3[] array, int newLength) {
+    if (newLength < 0)
+      newLength = array.length;
+    T3[] t = new T3[newLength];
+    if (array != null) {
+      int oldLength = array.length;
+      System.arraycopy(array, 0, t, 0, oldLength < newLength ? oldLength
+          : newLength);
+    }
+    return t;
+  }
+
+  public static float[] arrayCopyF(float[] array, int newLength) {
+    int oldLength = (array == null ? -1 : array.length);
+    if (newLength < 0) newLength = oldLength;
+    /**
+     * @j2sNative
+     * 
+     *     if (newLength < oldLength) return Clazz.array(-1, array, 0, newLength);
+     */
+    {}
+    float[] t = new float[newLength];
+    if (array != null) {
+      System.arraycopy(array, 0, t, 0, oldLength < newLength ? oldLength
+          : newLength);
+    }
+    return t;
+  }
+
+  public static int[] arrayCopyI(int[] array, int newLength) {
+    int oldLength = (array == null ? -1 : array.length);
+    if (newLength < 0) newLength = oldLength;
+    /**
+     * @j2sNative
+     * 
+     *     if (newLength < oldLength) return Clazz.array(-1, array, 0, newLength);
+     */
+    {}
+    int[] t = new int[newLength];
+    if (array != null) {
+      System.arraycopy(array, 0, t, 0, oldLength < newLength ? oldLength
+          : newLength);
+    }
+    return t;
+  }
+
+  /**
+   * a specialized method that allows copying from a starting point either
+   * to the end or to the middle (color schemes, especially)
+   * @param array
+   * @param i0
+   * @param n
+   * @return array or null
+   */
+  public static int[] arrayCopyRangeI(int[] array, int i0, int n) {
+    if (array == null)
+      return null;
+    int oldLength = array.length;
+    if (n == -1) n = oldLength;
+    if (n == -2) n = oldLength / 2;
+    /**
+     * @j2sNative
+     * 
+     * return Clazz.array(-1, array, i0, n);
+     * 
+     */
+    {
+    }
+    n -= i0;
+    int[] t = new int[n];
+    System.arraycopy(array, i0, t, 0, n);
+    return t;
+  }
+
+  public static int[] arrayCopyRangeRevI(int[] array, int i0, int n) {
+    if (array == null)
+      return null;
+    /**
+     * @j2sNative
+     * 
+     * return Clazz.array(-1, array, i0, n).reverse();
+     */
+    {
+    }
+    int[] t = arrayCopyRangeI(array, i0, n);
+    if (n < 0)
+      n = array.length;
+    for (int i = n / 2; --i >= 0;)
+      swapInt(t, i, n - 1 - i);
+    return t;
+  }
+
+  public static short[] arrayCopyShort(short[] array, int newLength) {
+    int oldLength = (array == null ? -1 : array.length);
+    if (newLength < 0) newLength = oldLength;
+    /**
+     * @j2sNative
+     * 
+     *     if (newLength < oldLength) return Clazz.array(-1, array, 0, newLength);
+     */
+    {}
+    short[] t = new short[newLength];
+    if (array != null) {
+      System.arraycopy(array, 0, t, 0, oldLength < newLength ? oldLength
+          : newLength);
+    }
+    return t;
+  }
+
+  public static byte[] arrayCopyByte(byte[] array, int newLength) {
+    int oldLength = (array == null ? -1 : array.length);
+    if (newLength < 0) newLength = oldLength;
+    /**
+     * @j2sNative
+     * 
+     *     if (newLength < oldLength) return Clazz.array(-1, array, 0, newLength);
+     */
+    {}
+    byte[] t = new byte[newLength];
+    if (array != null) {
+      System.arraycopy(array, 0, t, 0, oldLength < newLength ? oldLength
+          : newLength);
+    }
+    return t;
+  }
+
+  public static boolean[] arrayCopyBool(boolean[] array, int newLength) {
+    int oldLength = (array == null ? -1 : array.length);
+    if (newLength < 0) newLength = oldLength;
+    /**
+     * @j2sNative
+     * 
+     *     if (newLength < oldLength) return Clazz.array(-1, array, 0, newLength);
+     */
+    {}
+    boolean[] t = new boolean[newLength];
+    if (array != null) {
+      System.arraycopy(array, 0, t, 0, oldLength < newLength ? oldLength
+          : newLength);
+    }
+    return t;
+  }
+
+  public static void swapInt(int[] array, int indexA, int indexB) {
+    int t = array[indexA];
+    array[indexA] = array[indexB];
+    array[indexB] = t;
+  }
+
+  /*
+  public static void swap(short[] array, int indexA, int indexB) {
+    short t = array[indexA];
+    array[indexA] = array[indexB];
+    array[indexB] = t;
+  }
+
+  public static void swap(float[] array, int indexA, int indexB) {
+    float t = array[indexA];
+    array[indexA] = array[indexB];
+    array[indexB] = t;
+  }
+  */
+  
+  public static String dumpArray(String msg, float[][] A, int x1, int x2, int y1, int y2) {
+    String s = "dumpArray: " + msg + "\n";
+    for (int x = x1; x <= x2; x++)
+      s += "\t*" + x + "*";
+    for (int y = y2; y >= y1; y--) {
+      s += "\n*" + y + "*";
+      for (int x = x1; x <= x2; x++)
+        s += "\t" + (x < A.length && y < A[x].length ? A[x][y] : Float.NaN);
+    }
+    return s;
+  }
+
+  public static String dumpIntArray(int[] A, int n) {
+    String str = "";
+    for (int i = 0; i < n; i++)
+      str += " " + A[i];
+    return str;
+  }
+
+  public static String sortedItem(Lst<String> v, int n) {
+    if (v.size() == 0)
+      return null;
+    if (v.size() == 1)
+      return v.get(0);
+    String[] keys = v.toArray(new String[v.size()]);
+    Arrays.sort(keys);
+    return keys[n % keys.length];
+  }
+
+  /**
+   * Helper method for creating a List<Tx>[] without warnings.
+   * 
+   * @param <type> Type of objects in the list.
+   * @param size Array size.
+   * @return Array of List<type>
+   */
+  @SuppressWarnings("unchecked")
+  public static <type> Lst<type>[] createArrayOfArrayList(int size) {
+    return new Lst[size];
+  }
+
+  /**
+   * Helper method for creating a Map<K, V>[] without warnings.
+   * 
+   * @param <K> Type of object for the keys in the map.
+   * @param <V> Type of object for the values in the map.
+   * @param size Array size.
+   * @return Array of Map<K, V>
+   */
+  @SuppressWarnings("unchecked")
+  public static <K, V> Map<K, V>[] createArrayOfHashtable(int size) {
+    return new Hashtable[size];
+  }
+
+  public static void swap(Object[] o, int i, int j) {
+    Object oi = o[i];
+    o[i] = o[j];
+    o[j] = oi;
+  }
+
+  public static float[][] newFloat2(int n) {
+    return new float[n][];
+  }
+
+       public static boolean[][] newBool2(int n) {
+       return new boolean[n][];
+       }
+
+  public static int[][] newInt2(int n) {
+    return new int[n][];
+  }
+
+  public static int[][][] newInt3(int nx, int ny) {
+    return (ny < 0 ? new int[nx][][] : new int[nx][ny][]);
+  }
+
+  public static float[][][] newFloat3(int nx, int ny) {
+    return (ny < 0 ? new float[nx][][] : new float[nx][ny][]);
+  }
+
+  public static int[][][][] newInt4(int n) {
+    return new int[n][][][];
+  }
+
+  public static short[][] newShort2(int n) {
+    return new short[n][];
+  }
+
+  public static byte[][] newByte2(int n) {
+    return new byte[n][];
+  }
+
+  public static double[][] newDouble2(int n) {
+    return new double[n][];
+  }
+
+  /**
+   * remove all keys from a map that start with given root
+   * @param map
+   * @param root
+   * @return number removed
+   */
+  public static int removeMapKeys(Map<String, ?> map, String root) {
+    Lst<String> list = new Lst<String>();
+    for (String key: map.keySet())
+      if (key.startsWith(root))
+        list.addLast(key);
+    for (int i = list.size(); --i >= 0;)
+      map.remove(list.get(i));
+    return list.size();
+  }
+
+       public static boolean isAS(Object x) {
+               return x instanceof String[];
+       }
+
+       public static boolean isASS(Object x) {
+               return x instanceof String[][];
+       }
+
+       public static boolean isAP(Object x) {
+               return x instanceof T3[];
+       }
+
+       public static boolean isAF(Object x) {
+         return x instanceof float[];
+       }
+
+       public static boolean isAFloat(Object x) {
+         return x instanceof Float[];
+       }
+
+       public static boolean isAD(Object x) {
+         return x instanceof double[];
+       }
+
+       public static boolean isADD(Object x) {
+         return x instanceof double[][];
+       }
+
+       public static boolean isAB(Object x) {
+         return x instanceof byte[];
+       }
+
+       public static boolean isAI(Object x) {
+         return x instanceof int[];
+       }
+
+       public static boolean isAII(Object x) {
+         return (x instanceof int[][]);
+       }
+
+       public static boolean isAFF(Object x) {
+         return x instanceof float[][];
+       }
+
+       public static boolean isAFFF(Object x) {
+         return x instanceof float[][][];
+       }
+       
+       /**
+        * Ensure that we have signed and not unsigned bytes coming out of any
+        * process, but particularly out of file reading.
+        * 
+        * @param b
+        * @return b
+        */
+       public static byte[] ensureSignedBytes(byte[] b) {
+               if (b != null) {
+                       /**
+                        * @j2sNative
+                        * 
+                        *                      for (var i = b.length; --i >= 0;) { var j = b[i] &
+                        *            0xFF; if (j >= 0x80) j -= 0x100; b[i] = j; }
+                        * 
+                        */
+                       {
+                       }
+               }
+               return b;
+       }
+
+
+}
diff --git a/src/javajs/util/AjaxURLConnection.java b/src/javajs/util/AjaxURLConnection.java
new file mode 100644 (file)
index 0000000..0b038c3
--- /dev/null
@@ -0,0 +1,120 @@
+package javajs.util;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+import javajs.api.js.J2SObjectInterface;
+
+/**
+ * 
+ * A method to allow a JavaScript Ajax 
+ * 
+ */
+public class AjaxURLConnection extends URLConnection {
+
+  protected AjaxURLConnection(URL url) {
+    super(url);
+  }
+
+  byte[] bytesOut;
+  String postOut = "";
+
+  /**
+   * 
+   * doAjax() is where the synchronous call to AJAX is to happen. or at least
+   * where we wait for the asynchronous call to return. This method should fill
+   * the dataIn field with either a string or byte array, or null if you want to
+   * throw an error.
+   * 
+   * url, bytesOut, and postOut are all available for use
+   * 
+   * the method is "private", but in JavaScript that can still be overloaded.
+   * Just set something to org.jmol.awtjs.JmolURLConnection.prototype.doAjax
+   * 
+   * 
+   * @param isBinary 
+   * 
+   * @return file data as a javajs.util.SB or byte[] depending upon the file
+   *         type.
+   * 
+   * 
+   */
+  @SuppressWarnings("null")
+  private Object doAjax(boolean isBinary) {
+    J2SObjectInterface j2s = null;
+    /**
+     * @j2sNative
+     * 
+     *            j2s = J2S;
+     * 
+     */
+    {
+    }
+    return j2s._doAjax(url, postOut, bytesOut, isBinary);
+  }
+
+  @Override
+  public void connect() throws IOException {
+    // not expected to be used. 
+  }
+
+  public void outputBytes(byte[] bytes) {
+    //      type = "application/octet-stream;";
+    bytesOut = bytes;
+  }
+
+  public void outputString(String post) {
+    postOut = post;
+    //     type = "application/x-www-form-urlencoded";
+  }
+
+       @Override
+       public InputStream getInputStream() {
+               BufferedInputStream is = getAttachedStreamData(url, false);
+               return (is == null ? attachStreamData(url, doAjax(true)) : is);
+       }
+
+       /**
+        * J2S will attach the data (String, SB, or byte[]) to any URL that is 
+        * retrieved using a ClassLoader. This improves performance by
+        * not going back to the server every time a second time, since
+        * the first time in Java is usually just to see if it exists. 
+        * 
+        * @param url
+        * @return String, SB, or byte[]
+        */
+       public static BufferedInputStream getAttachedStreamData(URL url, boolean andDelete) {
+       
+               Object data = null;
+               /**
+                * @j2sNative
+                * 
+                *       data = url._streamData;
+                *       if (andDelete) url._streamData = null;
+                */
+               {
+               }
+               return (data == null ? null : Rdr.toBIS(data));
+       }
+
+   public static BufferedInputStream attachStreamData(URL url, Object o) {
+          /**
+           * @j2sNative
+           * 
+           *   url._streamData = o;
+           */
+          
+           return (o == null ? null : Rdr.toBIS(o));
+  }
+
+  /**
+   * @return javajs.util.SB or byte[], depending upon the file type
+   */
+  public Object getContents() {
+    return doAjax(false);
+  }
+
+}
diff --git a/src/javajs/util/AjaxURLStreamHandler.java b/src/javajs/util/AjaxURLStreamHandler.java
new file mode 100644 (file)
index 0000000..3d6d4d7
--- /dev/null
@@ -0,0 +1,53 @@
+package javajs.util;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+
+
+/**
+ * 
+ * A method to allow a JavaScript AJAX adapter to 
+ * deliver web content to JSmol. This handler is just a formality.
+ * 
+ */
+public class AjaxURLStreamHandler extends URLStreamHandler {
+
+       String protocol;
+
+       public AjaxURLStreamHandler(String protocol) {
+               this.protocol = protocol;
+       }
+
+       @Override
+       protected URLConnection openConnection(URL url) throws IOException {
+               return new AjaxURLConnection(url);
+       }
+
+
+  @Override
+  protected String toExternalForm(URL u) {
+    SB result = new SB();
+    result.append(u.getProtocol());
+    result.append(":");
+    if (u.getAuthority() != null && u.getAuthority().length() > 0) {
+      result.append("//");
+      result.append(u.getAuthority());
+    }
+    if (u.getPath() != null) {
+      result.append(u.getPath());
+    }
+    if (u.getQuery() != null) {
+      result.append("?");
+      result.append(u.getQuery());
+    }
+    if (u.getRef() != null) {
+      result.append("#");
+      result.append(u.getRef());
+    }
+    return result.toString();
+  }
+
+}
diff --git a/src/javajs/util/AjaxURLStreamHandlerFactory.java b/src/javajs/util/AjaxURLStreamHandlerFactory.java
new file mode 100644 (file)
index 0000000..ef903a2
--- /dev/null
@@ -0,0 +1,31 @@
+package javajs.util;
+
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+import java.util.Hashtable;
+import java.util.Map;
+
+
+/**
+ * 
+ * For handling URL file IO via AJAX in JavaScript version
+ * 
+ */
+
+public class AjaxURLStreamHandlerFactory implements URLStreamHandlerFactory {
+
+       Map<String, AjaxURLStreamHandler> htFactories = new Hashtable<String, AjaxURLStreamHandler>();
+       
+       public AjaxURLStreamHandlerFactory() {
+        // for reflection;
+       }
+       
+       @Override
+  public URLStreamHandler createURLStreamHandler(String protocol) {
+               AjaxURLStreamHandler fac = htFactories.get(protocol);
+               if (fac == null)
+                       htFactories.put(protocol, fac = new AjaxURLStreamHandler(protocol));
+               return (fac.protocol == null ? null : fac);
+       }
+
+}
diff --git a/src/javajs/util/ArrayDataReader.java b/src/javajs/util/ArrayDataReader.java
new file mode 100644 (file)
index 0000000..737a6ab
--- /dev/null
@@ -0,0 +1,57 @@
+package javajs.util;
+
+import java.io.IOException;
+
+
+
+
+
+
+/**
+ * 
+ * ArrayDataReader subclasses BufferedReader and overrides its
+ * read, readLine, mark, and reset methods so that JmolAdapter 
+ * works with String[] arrays without any further adaptation. 
+ * 
+ */
+
+public class ArrayDataReader extends DataReader {
+  private String[] data;
+  private int pt;
+  private int len;
+
+  public ArrayDataReader() {
+    super();
+  }
+  
+  @Override
+  public DataReader setData(Object data) {
+    this.data = (String[]) data;
+    len = this.data.length;
+    return this;
+  }
+
+  @Override
+  public int read(char[] buf, int off, int len) throws IOException {
+    return readBuf(buf, off, len);
+  }
+
+  @Override
+  public String readLine() {
+    return (pt < len ? data[pt++] : null);
+  }
+
+  /**
+   * 
+   * @param ptr
+   */
+  public void mark(long ptr) {
+    //ignore ptr.
+    ptMark = pt;
+  }
+
+  @Override
+  public void reset() {
+    pt = ptMark;
+  }
+}
\ No newline at end of file
diff --git a/src/javajs/util/BArray.java b/src/javajs/util/BArray.java
new file mode 100644 (file)
index 0000000..f09d272
--- /dev/null
@@ -0,0 +1,33 @@
+package javajs.util;
+
+public class BArray {
+  public byte[] data;
+  
+  public BArray(byte[] data) {
+    this.data = data;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o instanceof BArray) {
+      byte[] d = ((BArray) o).data;
+      if (d.length == data.length){
+        for (int i = 0; i < d.length; i++)
+          if (d[i] != data[i])
+            return false;
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return data.hashCode();
+  }
+  
+  @Override
+  public String toString() {
+    return new String(data);
+  }
+}
diff --git a/src/javajs/util/BC.java b/src/javajs/util/BC.java
new file mode 100644 (file)
index 0000000..5722efd
--- /dev/null
@@ -0,0 +1,186 @@
+package javajs.util;
+
+/**
+ * byte converter
+ * 
+ * 
+ * @author Bob Hanson hansonr@stolaf.edu
+ * 
+ */
+public class BC {
+
+  public BC() {
+    // unnecessary to instantialize unless subclassed
+  }
+  
+  public static float bytesToFloat(byte[] bytes, int j, boolean isBigEndian) throws Exception {
+    return intToFloat(bytesToInt(bytes, j, isBigEndian));
+  }
+
+  public static int bytesToShort(byte[] bytes, int j, boolean isBigEndian) {
+    int n = (isBigEndian ? (bytes[j + 1] & 0xff) | (bytes[j] & 0xff) << 8
+        : (bytes[j++] & 0xff) | (bytes[j++] & 0xff) << 8);
+      return (n > 0x7FFF ? n - 0x10000 : n);
+  }
+
+  public static int bytesToInt(byte[] bytes, int j, boolean isBigEndian) {
+    int n = (isBigEndian ? (bytes[j + 3] & 0xff) | (bytes[j + 2] & 0xff) << 8
+        | (bytes[j + 1] & 0xff) << 16 | (bytes[j] & 0xff) << 24
+        : (bytes[j++] & 0xff) | (bytes[j++] & 0xff) << 8
+            | (bytes[j++] & 0xff) << 16 | (bytes[j++] & 0xff) << 24);
+    /**
+     * @j2sNative
+     * 
+     * return (n > 0x7FFFFFFF ? n - 0x100000000 : n);
+     *   
+     */
+    {
+      return n;
+    }
+  }
+
+  public static int intToSignedInt(int n) {
+    /**
+     * @j2sNative
+     * 
+     * return (n > 0x7FFFFFFF ? n - 0x100000000 : n);
+     *   
+     */
+    {
+      return n;
+    }    
+  }
+  public static float intToFloat(int x) throws Exception {
+    /**
+     * see http://en.wikipedia.org/wiki/Binary32
+     * 
+     * [sign]      [8 bits power] [23 bits fraction]
+     * 0x80000000  0x7F800000      0x7FFFFF
+     * 
+     * (untested)
+     * 
+     * @j2sNative
+     * 
+     *       if (x == 0) return 0;
+     *       var o = javajs.util.BC;
+     *       if (o.fracIEEE == null)
+     *         o.setFracIEEE();
+     *       var m = ((x & 0x7F800000) >> 23);
+     *       return ((x & 0x80000000) == 0 ? 1 : -1) * o.shiftIEEE$D$I((x & 0x7FFFFF) | 0x800000, m - 149);
+     *  
+     */
+    {
+    return Float.intBitsToFloat(x);
+    }
+  }
+
+  /**
+   * see http://en.wikipedia.org/wiki/Binary64
+   *  
+   * not concerning ourselves with very small or very large numbers and getting
+   * this exactly right. Just need a float here.
+   * 
+   * @param bytes
+   * @param j
+   * @param isBigEndian
+   * @return float
+   */
+  public static float bytesToDoubleToFloat(byte[] bytes, int j, boolean isBigEndian) {
+    {
+      // IEEE754: sign (1 bit), exponent (11 bits), fraction (52 bits).
+      // seeeeeee eeeeffff ffffffff ffffffff ffffffff xxxxxxxx xxxxxxxx xxxxxxxx
+      //     b1      b2       b3       b4       b5    ---------float ignores----
+
+        if (fracIEEE == null)
+           setFracIEEE();
+        
+      /**
+       * @j2sNative
+       *       var b1, b2, b3, b4, b5;
+       *       
+       *       if (isBigEndian) {
+       *       b1 = bytes[j] & 0xFF;
+       *       b2 = bytes[j + 1] & 0xFF;
+       *       b3 = bytes[j + 2] & 0xFF;
+       *       b4 = bytes[j + 3] & 0xFF;
+       *       b5 = bytes[j + 4] & 0xFF;
+       *       } else {
+       *       b1 = bytes[j + 7] & 0xFF;
+       *       b2 = bytes[j + 6] & 0xFF;
+       *       b3 = bytes[j + 5] & 0xFF;
+       *       b4 = bytes[j + 4] & 0xFF;
+       *       b5 = bytes[j + 3] & 0xFF;
+       *       }
+       *       var s = ((b1 & 0x80) == 0 ? 1 : -1);
+       *       var e = (((b1 & 0x7F) << 4) | (b2 >> 4)) - 1026;
+       *       b2 = (b2 & 0xF) | 0x10;
+       *       return s * (C$.shiftIEEE$D$I(b2, e) +C$.shiftIEEE$D$I(b3, e - 8) + C$.shiftIEEE$D$I(b4, e - 16)
+       *         + C$.shiftIEEE$D$I(b5, e - 24));
+       */
+      {
+        double d;
+        
+        if (isBigEndian)
+          d = Double.longBitsToDouble((((long) bytes[j]) & 0xff) << 56
+             | (((long) bytes[j + 1]) & 0xff) << 48
+             | (((long) bytes[j + 2]) & 0xff) << 40
+             | (((long) bytes[j + 3]) & 0xff) << 32
+             | (((long) bytes[j + 4]) & 0xff) << 24
+             | (((long) bytes[j + 5]) & 0xff) << 16
+             | (((long) bytes[j + 6]) & 0xff) << 8 
+             | (((long) bytes[7]) & 0xff));
+        else
+          d = Double.longBitsToDouble((((long) bytes[j + 7]) & 0xff) << 56
+             | (((long) bytes[j + 6]) & 0xff) << 48
+             | (((long) bytes[j + 5]) & 0xff) << 40
+             | (((long) bytes[j + 4]) & 0xff) << 32
+             | (((long) bytes[j + 3]) & 0xff) << 24
+             | (((long) bytes[j + 2]) & 0xff) << 16
+             | (((long) bytes[j + 1]) & 0xff) << 8 
+             | (((long) bytes[j]) & 0xff));
+        return (float) d;
+      }
+
+    }
+  }
+
+  private static float[] fracIEEE;
+
+  private static void setFracIEEE() {
+    fracIEEE = new float[270];
+    for (int i = 0; i < 270; i++)
+      fracIEEE[i] = (float) Math.pow(2, i - 141);
+    //    System.out.println(fracIEEE[0] + "  " + Parser.FLOAT_MIN_SAFE);
+    //    System.out.println(fracIEEE[269] + "  " + Float.MAX_VALUE);
+  }
+
+  /**
+   * only concerned about reasonable float values here -- private but not designated; called by JavaScript
+   * 
+   * @param f
+   * @param i
+   * @return f * 2^i
+   */
+  static double shiftIEEE(double f, int i) {
+    if (f == 0 || i < -140)
+      return 0;
+    if (i > 128)
+      return Float.MAX_VALUE;
+    return f * fracIEEE[i + 140];
+  }
+
+//  static {
+//    setFracIEEE();
+//    for (int i = -50; i < 50; i++) {
+//      float f = i * (float) (Math.random() * Math.pow(2, Math.random() * 100 - 50));
+//      int x = Float.floatToIntBits(f);
+//      int m = ((x & 0x7F800000) >> 23);
+//      float f1 = (float) (f == 0 ? 0 : ((x & 0x80000000) == 0 ? 1 : -1) * shiftIEEE((x & 0x7FFFFF) | 0x800000, m - 149));
+//      System.out.println(f + "  " + f1);
+//    }
+//    System.out.println("binarydo");
+//  }
+
+
+
+}
diff --git a/src/javajs/util/BS.java b/src/javajs/util/BS.java
new file mode 100644 (file)
index 0000000..1f3aaa4
--- /dev/null
@@ -0,0 +1,960 @@
+/*
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright 1995-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javajs.util;
+
+import javajs.api.JSONEncodable;
+
+
+
+/**
+ * 
+ * a fast 32-bit BitSet optimized for Java2Script -- about 25 times faster than
+ * java.util.BitSet
+ * 
+ * @author Bob Hanson hansonr@stolaf.edu
+ * 
+ *         Additions by Bob Hanson to allow for JavaScript mix of int/long Note
+ *         that Firefox (Sept 2012) does not really treat "Int32Array" as such,
+ *         because any element can be pushed into being a 64-bit number, which
+ *         really isn't because the last 8 bits are not usable.
+ * 
+ *         This class implements a vector of bits that grows as needed. Each
+ *         component of the bit set has a {@code boolean} value. The bits of a
+ *         {@code BitSet} are indexed by nonnegative integers. Individual
+ *         indexed bits can be examined, set, or cleared. One {@code BitSet} may
+ *         be used to modify the contents of another {@code BitSet} through
+ *         logical AND, logical inclusive OR, and logical exclusive OR
+ *         operations.
+ * 
+ *         <p>
+ *         By default, all bits in the set initially have the value {@code
+ *         false}.
+ * 
+ *         <p>
+ *         Every bit set has a current size, which is the number of bits of
+ *         space currently in use by the bit set. Note that the size is related
+ *         to the implementation of a bit set, so it may change with
+ *         implementation. The length of a bit set relates to logical length of
+ *         a bit set and is defined independently of implementation.
+ * 
+ *         <p>
+ *         Unless otherwise noted, passing a null parameter to any of the
+ *         methods in a {@code BitSet} will result in a {@code
+ *         NullPointerException}.
+ * 
+ *         <p>
+ *         A {@code BitSet} is not safe for multithreaded use without external
+ *         synchronization.
+ * 
+ * @author Arthur van Hoff
+ * @author Michael McCloskey
+ * @author Martin Buchholz
+ * @since JDK1.0
+ */
+public class BS implements Cloneable, JSONEncodable {
+  /*
+   * BitSets are packed into arrays of "words."
+   * 
+   * An int, which consists of 32 bits, requiring 5 address bits, is used for
+   * the JavaScript port.
+   */
+  private final static int ADDRESS_BITS_PER_WORD = 5;
+  private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
+  protected final static int BIT_INDEX_MASK = BITS_PER_WORD - 1;
+
+  /* Used to shift left or right for a partial word mask */
+  protected static final int WORD_MASK = 0xffffffff;
+
+
+  /**
+   * The internal field corresponding to the serialField "bits".
+   */
+  protected int[] words;
+
+  /**
+   * The number of words in the logical size of this BitSet.
+   */
+  protected transient int wordsInUse = 0;
+
+  /**
+   * Whether the size of "words" is user-specified. If so, we assume the user
+   * knows what he's doing and try harder to preserve it.
+   */
+  private transient boolean sizeIsSticky = false;
+
+  /* use serialVersionUID from JDK 1.0.2 for interoperability */
+  //private static final long serialVersionUID = 7997698588986878753L;
+
+  /**
+   * Given a bit index, return word index containing it.
+   * @param bitIndex 
+   * @return b
+   */
+  protected static int wordIndex(int bitIndex) {
+    return bitIndex >> ADDRESS_BITS_PER_WORD;
+  }
+
+  /**
+   * Sets the field wordsInUse to the logical size in words of the bit set.
+   * WARNING:This method assumes that the number of words actually in use is
+   * less than or equal to the current value of wordsInUse!
+   */
+  protected void recalculateWordsInUse() {
+    // Traverse the bitset until a used word is found
+    int i;
+    for (i = wordsInUse - 1; i >= 0; i--)
+      if (words[i] != 0)
+        break;
+
+    wordsInUse = i + 1; // The new logical size
+  }
+
+  /**
+   * Creates a new bit set. All bits are initially {@code false}.
+   */
+  public BS() {
+    initWords(BITS_PER_WORD);
+    sizeIsSticky = false;
+  }
+
+  /**
+   * Creates a bit set whose initial size is large enough to explicitly
+   * represent bits with indices in the range {@code 0} through {@code nbits-1}.
+   * All bits are initially {@code false}.
+   * 
+   * @param nbits
+   *          the initial size of the bit set
+   * @return bs
+   * @throws NegativeArraySizeException
+   *           if the specified initial size is negative
+   */
+  public static BS newN(int nbits) {
+    BS bs = new BS();
+    bs.init(nbits);
+    return bs;
+  }
+
+  protected void init(int nbits) {
+    // nbits can't be negative; size 0 is OK
+    if (nbits < 0)
+      throw new NegativeArraySizeException("nbits < 0: " + nbits);
+    initWords(nbits);
+    sizeIsSticky = true;
+  }
+
+  private void initWords(int nbits) {
+    words = new int[wordIndex(nbits - 1) + 1];
+  }
+
+  /**
+   * Ensures that the BitSet can hold enough words.
+   * 
+   * @param wordsRequired
+   *          the minimum acceptable number of words.
+   */
+  private void ensureCapacity(int wordsRequired) {
+    if (words.length < wordsRequired) {
+      // Allocate larger of doubled size or required size
+      int request = Math.max(2 * words.length, wordsRequired);
+      setLength(request);
+      sizeIsSticky = false;
+    }
+  }
+
+  /**
+   * Ensures that the BitSet can accommodate a given wordIndex, temporarily
+   * violating the invariants. The caller must restore the invariants before
+   * returning to the user, possibly using recalculateWordsInUse().
+   * 
+   * @param wordIndex
+   *          the index to be accommodated.
+   */
+  protected void expandTo(int wordIndex) {
+    int wordsRequired = wordIndex + 1;
+    if (wordsInUse < wordsRequired) {
+      ensureCapacity(wordsRequired);
+      wordsInUse = wordsRequired;
+    }
+  }
+
+
+  /**
+   * Sets the bit at the specified index to {@code true}.
+   * 
+   * @param bitIndex
+   *          a bit index
+   * @throws IndexOutOfBoundsException
+   *           if the specified index is negative
+   * @since JDK1.0
+   */
+  public void set(int bitIndex) {
+    if (bitIndex < 0)
+      throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
+
+    int wordIndex = wordIndex(bitIndex);
+    expandTo(wordIndex);
+
+    words[wordIndex] |= (1 << bitIndex); // Restores invariants
+
+  }
+
+  /**
+   * Sets the bit at the specified index to the specified value.
+   * 
+   * @param bitIndex
+   *          a bit index
+   * @param value
+   *          a boolean value to set
+   * @throws IndexOutOfBoundsException
+   *           if the specified index is negative
+   * @since 1.4
+   */
+  public void setBitTo(int bitIndex, boolean value) {
+    if (value)
+      set(bitIndex);
+    else
+      clear(bitIndex);
+  }
+
+  /**
+   * Sets the bits from the specified {@code fromIndex} (inclusive) to the
+   * specified {@code toIndex} (exclusive) to {@code true}.
+   * 
+   * @param fromIndex
+   *          index of the first bit to be set
+   * @param toIndex
+   *          index after the last bit to be set
+   * @throws IndexOutOfBoundsException
+   *           if {@code fromIndex} is negative, or {@code toIndex} is negative,
+   *           or {@code fromIndex} is larger than {@code toIndex}
+   * @since 1.4
+   */
+  public void setBits(int fromIndex, int toIndex) {
+
+    if (fromIndex == toIndex)
+      return;
+
+    // Increase capacity if necessary
+    int startWordIndex = wordIndex(fromIndex);
+    int endWordIndex = wordIndex(toIndex - 1);
+    expandTo(endWordIndex);
+
+    int firstWordMask = WORD_MASK << fromIndex;
+    int lastWordMask = WORD_MASK >>> -toIndex;
+    if (startWordIndex == endWordIndex) {
+      // Case 1: One word
+      words[startWordIndex] |= (firstWordMask & lastWordMask);
+    } else {
+      // Case 2: Multiple words
+      // Handle first word
+      words[startWordIndex] |= firstWordMask;
+
+      // Handle intermediate words, if any
+      for (int i = startWordIndex + 1; i < endWordIndex; i++)
+        words[i] = WORD_MASK;
+
+      // Handle last word (restores invariants)
+      words[endWordIndex] |= lastWordMask;
+    }
+  }
+
+  /**
+   * Sets the bit specified by the index to {@code false}.
+   * 
+   * @param bitIndex
+   *          the index of the bit to be cleared
+   * @throws IndexOutOfBoundsException
+   *           if the specified index is negative
+   * @since JDK1.0
+   */
+  public void clear(int bitIndex) {
+    if (bitIndex < 0)
+      throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
+
+    int wordIndex = wordIndex(bitIndex);
+    if (wordIndex >= wordsInUse)
+      return;
+
+    words[wordIndex] &= ~(1 << bitIndex);
+
+    recalculateWordsInUse();
+  }
+
+  /**
+   * Sets the bits from the specified {@code fromIndex} (inclusive) to the
+   * specified {@code toIndex} (exclusive) to {@code false}.
+   * 
+   * @param fromIndex
+   *          index of the first bit to be cleared
+   * @param toIndex
+   *          index after the last bit to be cleared
+   * @throws IndexOutOfBoundsException
+   *           if {@code fromIndex} is negative, or {@code toIndex} is negative,
+   *           or {@code fromIndex} is larger than {@code toIndex}
+   * @since 1.4
+   */
+  public void clearBits(int fromIndex, int toIndex) {
+    if (fromIndex == toIndex)
+      return;
+
+    int startWordIndex = wordIndex(fromIndex);
+    if (startWordIndex >= wordsInUse)
+      return;
+
+    int endWordIndex = wordIndex(toIndex - 1);
+    if (endWordIndex >= wordsInUse) {
+      toIndex = length();
+      endWordIndex = wordsInUse - 1;
+    }
+
+    int firstWordMask = WORD_MASK << fromIndex;
+    int lastWordMask = WORD_MASK >>> -toIndex;
+    if (startWordIndex == endWordIndex) {
+      // Case 1: One word
+      words[startWordIndex] &= ~(firstWordMask & lastWordMask);
+    } else {
+      // Case 2: Multiple words
+      // Handle first word
+      words[startWordIndex] &= ~firstWordMask;
+
+      // Handle intermediate words, if any
+      for (int i = startWordIndex + 1; i < endWordIndex; i++)
+        words[i] = 0;
+
+      // Handle last word
+      words[endWordIndex] &= ~lastWordMask;
+    }
+
+    recalculateWordsInUse();
+  }
+
+  /**
+   * Sets all of the bits in this BitSet to {@code false}.
+   * 
+   * @since 1.4
+   */
+  public void clearAll() {
+    while (wordsInUse > 0)
+      words[--wordsInUse] = 0;
+  }
+
+  /**
+   * Returns the value of the bit with the specified index. The value is {@code
+   * true} if the bit with the index {@code bitIndex} is currently set in this
+   * {@code BitSet}; otherwise, the result is {@code false}.
+   * 
+   * @param bitIndex
+   *          the bit index
+   * @return the value of the bit with the specified index
+   * @throws IndexOutOfBoundsException
+   *           if the specified index is negative
+   */
+  public boolean get(int bitIndex) {
+    if (bitIndex < 0)
+      throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
+
+    int wordIndex = wordIndex(bitIndex);
+    return (wordIndex < wordsInUse)
+        && ((words[wordIndex] & (1 << bitIndex)) != 0);
+  }
+
+  /**
+   * Returns the index of the first bit that is set to {@code true} that occurs
+   * on or after the specified starting index. If no such bit exists then
+   * {@code -1} is returned.
+   * 
+   * <p>
+   * To iterate over the {@code true} bits in a {@code BitSet}, use the
+   * following loop:
+   * 
+   * <pre>
+   * @code
+   * for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
+   *     // operate on index i here
+   * }}
+   * </pre>
+   * 
+   * @param fromIndex
+   *          the index to start checking from (inclusive)
+   * @return the index of the next set bit, or {@code -1} if there is no such
+   *         bit
+   * @throws IndexOutOfBoundsException
+   *           if the specified index is negative
+   * @since 1.4
+   */
+  public int nextSetBit(int fromIndex) {
+    if (fromIndex < 0)
+      throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
+
+    int u = wordIndex(fromIndex);
+    if (u >= wordsInUse)
+      return -1;
+
+    int word = words[u] & (WORD_MASK << fromIndex);
+
+    while (true) {
+      if (word != 0)
+        return (u * BITS_PER_WORD) + Integer.numberOfTrailingZeros(word);
+      if (++u == wordsInUse)
+        return -1;
+      word = words[u];
+    }
+  }
+
+  /**
+   * Returns the index of the first bit that is set to {@code false} that occurs
+   * on or after the specified starting index.
+   * 
+   * @param fromIndex
+   *          the index to start checking from (inclusive)
+   * @return the index of the next clear bit
+   * @throws IndexOutOfBoundsException
+   *           if the specified index is negative
+   * @since 1.4
+   */
+  public int nextClearBit(int fromIndex) {
+    // Neither spec nor implementation handle bitsets of maximal length.
+    // See 4816253.
+    if (fromIndex < 0)
+      throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
+
+    int u = wordIndex(fromIndex);
+    if (u >= wordsInUse)
+      return fromIndex;
+
+    int word = ~words[u] & (WORD_MASK << fromIndex);
+
+    while (true) {
+      if (word != 0)
+        return (u * BITS_PER_WORD) + Integer.numberOfTrailingZeros(word);
+      if (++u == wordsInUse)
+        return wordsInUse * BITS_PER_WORD;
+      word = ~words[u];
+    }
+  }
+
+  /**
+   * Returns the "logical size" of this {@code BitSet}: the index of the highest
+   * set bit in the {@code BitSet} plus one. Returns zero if the {@code BitSet}
+   * contains no set bits.
+   * 
+   * @return the logical size of this {@code BitSet}
+   * @since 1.2
+   */
+  public int length() {
+    if (wordsInUse == 0)
+      return 0;
+
+    return BITS_PER_WORD * (wordsInUse - 1)
+        + (BITS_PER_WORD - Integer.numberOfLeadingZeros(words[wordsInUse - 1]));
+  }
+
+  /**
+   * Returns true if this {@code BitSet} contains no bits that are set to
+   * {@code true}.
+   * 
+   * @return boolean indicating whether this {@code BitSet} is empty
+   * @since 1.4
+   */
+  public boolean isEmpty() {
+    return wordsInUse == 0;
+  }
+
+  /**
+   * Returns true if the specified {@code BitSet} has any bits set to {@code
+   * true} that are also set to {@code true} in this {@code BitSet}.
+   * 
+   * @param set
+   *          {@code BitSet} to intersect with
+   * @return boolean indicating whether this {@code BitSet} intersects the
+   *         specified {@code BitSet}
+   * @since 1.4
+   */
+  public boolean intersects(BS set) {
+    for (int i = Math.min(wordsInUse, set.wordsInUse) - 1; i >= 0; i--)
+      if ((words[i] & set.words[i]) != 0)
+        return true;
+    return false;
+  }
+
+  /**
+   * Returns the number of bits set to {@code true} in this {@code BitSet}.
+   * 
+   * @return the number of bits set to {@code true} in this {@code BitSet}
+   * @since 1.4
+   */
+  public int cardinality() {
+    int sum = 0;
+    for (int i = 0; i < wordsInUse; i++)
+      sum += Integer.bitCount(words[i]);
+    return sum;
+  }
+
+  /**
+   * Performs a logical <b>AND</b> of this target bit set with the argument bit
+   * set. This bit set is modified so that each bit in it has the value {@code
+   * true} if and only if it both initially had the value {@code true} and the
+   * corresponding bit in the bit set argument also had the value {@code true}.
+   * 
+   * @param set
+   *          a bit set
+   */
+  public void and(BS set) {
+    if (this == set)
+      return;
+
+    while (wordsInUse > set.wordsInUse)
+      words[--wordsInUse] = 0;
+
+    // Perform logical AND on words in common
+    for (int i = 0; i < wordsInUse; i++)
+      words[i] &= set.words[i];
+
+    recalculateWordsInUse();
+  }
+
+  /**
+   * Performs a logical <b>OR</b> of this bit set with the bit set argument.
+   * This bit set is modified so that a bit in it has the value {@code true} if
+   * and only if it either already had the value {@code true} or the
+   * corresponding bit in the bit set argument has the value {@code true}.
+   * 
+   * @param set
+   *          a bit set
+   */
+  public void or(BS set) {
+    if (this == set)
+      return;
+
+    int wordsInCommon = Math.min(wordsInUse, set.wordsInUse);
+
+    if (wordsInUse < set.wordsInUse) {
+      ensureCapacity(set.wordsInUse);
+      wordsInUse = set.wordsInUse;
+    }
+
+    // Perform logical OR on words in common
+    for (int i = 0; i < wordsInCommon; i++)
+      words[i] |= set.words[i];
+
+    // Copy any remaining words
+    if (wordsInCommon < set.wordsInUse)
+      System.arraycopy(set.words, wordsInCommon, words, wordsInCommon,
+          wordsInUse - wordsInCommon);
+
+  }
+
+  /**
+   * Performs a logical <b>XOR</b> of this bit set with the bit set argument.
+   * This bit set is modified so that a bit in it has the value {@code true} if
+   * and only if one of the following statements holds:
+   * <ul>
+   * <li>The bit initially has the value {@code true}, and the corresponding bit
+   * in the argument has the value {@code false}.
+   * <li>The bit initially has the value {@code false}, and the corresponding
+   * bit in the argument has the value {@code true}.
+   * </ul>
+   * 
+   * @param set
+   *          a bit set
+   */
+  public void xor(BS set) {
+    int wordsInCommon = Math.min(wordsInUse, set.wordsInUse);
+
+    if (wordsInUse < set.wordsInUse) {
+      ensureCapacity(set.wordsInUse);
+      wordsInUse = set.wordsInUse;
+    }
+
+    // Perform logical XOR on words in common
+    for (int i = 0; i < wordsInCommon; i++)
+      words[i] ^= set.words[i];
+
+    // Copy any remaining words
+    if (wordsInCommon < set.wordsInUse)
+      System.arraycopy(set.words, wordsInCommon, words, wordsInCommon,
+          set.wordsInUse - wordsInCommon);
+
+    recalculateWordsInUse();
+  }
+
+  /**
+   * Clears all of the bits in this {@code BitSet} whose corresponding bit is
+   * set in the specified {@code BitSet}.
+   * 
+   * @param set
+   *          the {@code BitSet} with which to mask this {@code BitSet}
+   * @since 1.2
+   */
+  public void andNot(BS set) {
+    // Perform logical (a & !b) on words in common
+    for (int i = Math.min(wordsInUse, set.wordsInUse) - 1; i >= 0; i--)
+      words[i] &= ~set.words[i];
+
+    recalculateWordsInUse();
+  }
+
+  /**
+   * Returns a hash code value for this bit set. The hash code depends only on
+   * which bits have been set within this <code>BitSet</code>. The algorithm
+   * used to compute it may be described as follows.
+   * <p>
+   * Suppose the bits in the <code>BitSet</code> were to be stored in an array
+   * of <code>long</code> integers called, say, <code>words</code>, in such a
+   * manner that bit <code>k</code> is set in the <code>BitSet</code> (for
+   * nonnegative values of <code>k</code>) if and only if the expression
+   * 
+   * <pre>
+   * ((k &gt;&gt; 6) &lt; words.length) &amp;&amp; ((words[k &gt;&gt; 6] &amp; (1 &lt;&lt; (bit &amp; 0x3F))) != 0)
+   * </pre>
+   * 
+   * is true. Then the following definition of the <code>hashCode</code> method
+   * would be a correct implementation of the actual algorithm:
+   * 
+   * <pre>
+   * public int hashCode() {
+   *  long h = 1234;
+   *  for (int i = words.length; --i &gt;= 0;) {
+   *    h &circ;= words[i] * (i + 1);
+   *  }
+   *  return (int) ((h &gt;&gt; 32) &circ; h);
+   * }
+   * </pre>
+   * 
+   * Note that the hash code values change if the set of bits is altered.
+   * <p>
+   * Overrides the <code>hashCode</code> method of <code>Object</code>.
+   * 
+   * @return a hash code value for this bit set.
+   */
+  @Override
+  public int hashCode() {
+    long h = 1234;
+    for (int i = wordsInUse; --i >= 0;)
+      h ^= words[i] * (i + 1);
+
+    return (int) ((h >> 32) ^ h);
+  }
+
+  /**
+   * Returns the number of bits of space actually in use by this {@code BitSet}
+   * to represent bit values. The maximum element in the set is the size - 1st
+   * element.
+   * 
+   * @return the number of bits currently in this bit set
+   */
+  public int size() {
+    return words.length * BITS_PER_WORD;
+  }
+
+  /**
+   * Compares this object against the specified object. The result is {@code
+   * true} if and only if the argument is not {@code null} and is a {@code
+   * Bitset} object that has exactly the same set of bits set to {@code true} as
+   * this bit set. That is, for every nonnegative {@code int} index {@code k},
+   * 
+   * <pre>
+   * ((BitSet) obj).get(k) == this.get(k)
+   * </pre>
+   * 
+   * must be true. The current sizes of the two bit sets are not compared.
+   * 
+   * @param obj
+   *          the object to compare with
+   * @return {@code true} if the objects are the same; {@code false} otherwise
+   * @see #size()
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof BS))
+      return false;
+    if (this == obj)
+      return true;
+
+    BS set = (BS) obj;
+
+    if (wordsInUse != set.wordsInUse)
+      return false;
+
+    // Check words in use by both BitSets
+    for (int i = 0; i < wordsInUse; i++)
+      if (words[i] != set.words[i])
+        return false;
+
+    return true;
+  }
+
+  /**
+   * Cloning this {@code BitSet} produces a new {@code BitSet} that is equal to
+   * it. The clone of the bit set is another bit set that has exactly the same
+   * bits set to {@code true} as this bit set.
+   * 
+   * @return a clone of this bit set
+   * @see #size()
+   */
+  @Override
+  public Object clone() {
+    if (!sizeIsSticky && wordsInUse != words.length)
+      setLength(wordsInUse);
+    return copy(this);
+  }
+
+  /**
+   * Attempts to reduce internal storage used for the bits in this bit set.
+   * Calling this method may, but is not required to, affect the value returned
+   * by a subsequent call to the {@link #size()} method.
+   * @param n 
+   */
+  private void setLength(int n) {
+    /**
+     * @j2sNative
+     *     if (n == this.words.length) return;
+     *     if (n == this.wordsInUse) {
+     *      this.words = Clazz.array(-1, this.words, 0, n);
+     *      return;
+     *     }
+     */
+    {}
+    int[] a = new int[n];
+    System.arraycopy(words, 0, a, 0, wordsInUse);
+    words = a;    
+  }
+
+  /**
+   * Returns a string representation of this bit set. For every index for which
+   * this {@code BitSet} contains a bit in the set state, the decimal
+   * representation of that index is included in the result. Such indices are
+   * listed in order from lowest to highest, separated by ",&nbsp;" (a comma and
+   * a space) and surrounded by braces, resulting in the usual mathematical
+   * notation for a set of integers.
+   * 
+   * <p>
+   * Example:
+   * 
+   * <pre>
+   * BitSet drPepper = new BitSet();
+   * </pre>
+   * 
+   * Now {@code drPepper.toString()} returns "{}".
+   * <p>
+   * 
+   * <pre>
+   * drPepper.set(2);
+   * </pre>
+   * 
+   * Now {@code drPepper.toString()} returns "{2}".
+   * <p>
+   * 
+   * <pre>
+   * drPepper.set(4);
+   * drPepper.set(10);
+   * </pre>
+   * 
+   * Now {@code drPepper.toString()} returns "{2, 4, 10}".
+   * 
+   * @return a string representation of this bit set
+   */
+  @Override
+  public String toString() {
+    return escape(this, '(', ')');
+  }
+  
+  private final static int[] emptyBitmap = new int[0];
+
+  /**
+   * fast copy
+   * 
+   * @param bitsetToCopy
+   * @return bs
+   */
+  public static BS copy(BS bitsetToCopy) {
+    BS bs;
+    /**
+     * Clazz.clone will copy wordsInUse and sizeIsSticky, 
+     * but just a pointer to the words array.
+     * 
+     * @j2sNative
+     * 
+     *            bs = Clazz.clone(bitsetToCopy);
+     * 
+     */
+    {
+      bs = new BS();
+    }
+    int wordCount = bitsetToCopy.wordsInUse;
+    if (wordCount == 0) {
+      bs.words = emptyBitmap;
+    } else {
+      
+      /**
+       * Clazz.clone will copy wordsInUse and sizeIsSticky, 
+       * but just a pointer to the words array.
+       * 
+       * @j2sNative
+       * 
+       *   bs.words = Clazz.array(-1, bitsetToCopy.words, 0, bs.wordsInUse = wordCount);
+       * 
+       */
+      {
+        bs.words = new int[bs.wordsInUse = wordCount];
+        System.arraycopy(bitsetToCopy.words, 0, bs.words, 0, wordCount);
+      }
+
+    }
+    return bs;
+  }
+
+  /**
+   * 
+   * @param max
+   * @return n bits below max
+   */
+  public int cardinalityN(int max) {
+    int n = cardinality();
+    for (int i = length(); --i >= max;)
+      if (get(i))
+        n--;
+    return n;
+  }
+
+  @Override
+  public String toJSON() {
+
+    int numBits = (wordsInUse > 128 ? cardinality() : wordsInUse
+        * BITS_PER_WORD);
+    SB b = SB.newN(6 * numBits + 2);
+    b.appendC('[');
+
+    int i = nextSetBit(0);
+    if (i != -1) {
+      b.appendI(i);
+      for (i = nextSetBit(i + 1); i >= 0; i = nextSetBit(i + 1)) {
+        int endOfRun = nextClearBit(i);
+        do {
+          b.append(", ").appendI(i);
+        } while (++i < endOfRun);
+      }
+    }
+
+    b.appendC(']');
+    return b.toString();
+  }
+
+  public static String escape(BS bs, char chOpen, char chClose) {
+    if (bs == null)
+      return chOpen + "{}" + chClose;
+    SB s = new SB();
+    s.append(chOpen + "{");
+    int imax = bs.length();
+    int iLast = -1;
+    int iFirst = -2;
+    int i = -1;
+    while (++i <= imax) {
+      boolean isSet = bs.get(i);
+      if (i == imax || iLast >= 0 && !isSet) {
+        if (iLast >= 0 && iFirst != iLast)
+          s.append((iFirst == iLast - 1 ? " " : ":") + iLast);
+        if (i == imax)
+          break;
+        iLast = -1;
+      }
+      if (bs.get(i)) {
+        if (iLast < 0) {
+          s.append((iFirst == -2 ? "" : " ") + i);
+          iFirst = i;
+        }
+        iLast = i;
+      }
+    }
+    s.append("}").appendC(chClose);
+    return s.toString();
+  }
+
+  public static BS unescape(String str) {
+    char ch;
+    int len;
+    if (str == null || (len = (str = str.trim()).length()) < 4
+        || str.equalsIgnoreCase("({null})") 
+        || (ch = str.charAt(0)) != '(' && ch != '[' 
+        || str.charAt(len - 1) != (ch == '(' ? ')' : ']')
+        || str.charAt(1) != '{' || str.indexOf('}') != len - 2)
+      return null;
+    len -= 2;
+    for (int i = len; --i >= 2;)
+      if (((ch = str.charAt(i)) < 48 || ch > 57) && ch != ' ' && ch != '\t'
+          && ch != ':')
+        return null;
+    int lastN = len;
+    while (48 <= (ch = str.charAt(--lastN)) && ch <= 57) {
+      // loop
+    }
+    if (++lastN == len)
+      lastN = 0;
+    else
+      try {
+        lastN = Integer.parseInt(str.substring(lastN, len));
+      } catch (NumberFormatException e) {
+        return null;
+      }
+    BS bs = BS.newN(lastN);
+    lastN = -1;
+    int iPrev = -1;
+    int iThis = -2;
+    for (int i = 2; i <= len; i++) {
+      switch (ch = str.charAt(i)) {
+      case '\t':
+      case ' ':
+      case '}':
+        if (iThis < 0)
+          break;
+        if (iThis < lastN)
+          return null;
+        lastN = iThis;
+        if (iPrev < 0)
+          iPrev = iThis;
+        bs.setBits(iPrev, iThis + 1);
+        iPrev = -1;
+        iThis = -2;
+        break;
+      case ':':
+        iPrev = lastN = iThis;
+        iThis = -2;
+        break;
+      default:
+        if (48 <= ch && ch <= 57) {
+          if (iThis < 0)
+            iThis = 0;
+          iThis = (iThis * 10) + (ch - 48);
+        }
+      }
+    }
+    return (iPrev >= 0 ? null : bs);
+  }
+
+}
diff --git a/src/javajs/util/Base64.java b/src/javajs/util/Base64.java
new file mode 100644 (file)
index 0000000..a7ac3a9
--- /dev/null
@@ -0,0 +1,120 @@
+// Version 1.0a
+// Copyright (C) 1998, James R. Weeks and BioElectroMech.
+// Visit BioElectroMech at www.obrador.com.  Email James@obrador.com.
+
+// See license.txt for details about the allowed used of this software.
+// This software is based in part on the work of the Independent JPEG Group.
+// See IJGreadme.txt for details about the Independent JPEG Group's license.
+
+// This encoder is inspired by the Java Jpeg encoder by Florian Raemy,
+// studwww.eurecom.fr/~raemy.
+// It borrows a great deal of code and structure from the Independent
+// Jpeg Group's Jpeg 6a library, Copyright Thomas G. Lane.
+// See license.txt for details.
+
+package javajs.util;
+
+
+public class Base64 {
+
+  //                              0         1         2         3         4         5         6
+  //                              0123456789012345678901234567890123456789012345678901234567890123
+  private static String base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  //                              41----------------------5A
+  //                                                        61----------------------7A
+  //                                                                                  30------39    
+  //                                                                                            2B  
+  //                                                                                             2F
+  //                                                 alternative "URL-SAFE"     2D and 5F       -_
+  
+  private static int[] decode64 = new int[] {
+    0,0,0,0,     0,0,0,0,     0,0,0,0,     0,0,0,0,      //0x00-0x0F
+    0,0,0,0,     0,0,0,0,     0,0,0,0,     0,0,0,0,      //0x10-0x1F
+    0,0,0,0,     0,0,0,0,     0,0,0,62,    0,62,0,63,    //0x20-0x2F
+    52,53,54,55, 56,57,58,59, 60,61,0,0,   0,0,0,0,      //0x30-0x3F
+    0,0,1,2,     3,4,5,6,     7,8,9,10,    11,12,13,14,  //0x40-0x4F
+    15,16,17,18, 19,20,21,22, 23,24,25,0,  0,0,0,63,     //0x50-0x5F
+    0,26,27,28,  29,30,31,32, 33,34,35,36, 37,38,39,40,  //0x60-0x6F
+    41,42,43,44, 45,46,47,48, 49,50,51,0,  0,0,0,0,      //0x70-0x7F
+  };
+    
+//  public static void write(byte[] bytes, OutputChannel out) {
+//    SB sb = getBase64(bytes);
+//    int len = sb.length();
+//    byte[] b = new byte[1];
+//    for (int i = 0; i < len; i++) {
+//      b[0] = (byte) sb.charAt(i);
+//      out.write(b, 0, 1);
+//    }
+//  }
+
+  public static byte[] getBytes64(byte[] bytes) {
+    return getBase64(bytes).toBytes(0, -1);
+  }
+
+  /**
+   * 
+   * @param bytes
+   * @return BASE64-encoded string, without ";base64,"
+   */
+  public static SB getBase64(byte[] bytes) {
+    long nBytes = bytes.length;
+    SB sout = new SB();
+    if (nBytes == 0)
+      return sout;
+    for (int i = 0, nPad = 0; i < nBytes && nPad == 0;) {
+      if (i % 75 == 0 && i != 0)
+        sout.append("\r\n");
+      nPad = (i + 2 == nBytes ? 1 : i + 1 == nBytes ? 2 : 0);
+      int outbytes = ((bytes[i++] << 16) & 0xFF0000)
+          | ((nPad == 2 ? 0 : bytes[i++] << 8) & 0x00FF00)
+          | ((nPad >= 1 ? 0 : (int) bytes[i++]) & 0x0000FF);
+      //System.out.println(Integer.toHexString(outbytes));
+      sout.appendC(base64.charAt((outbytes >> 18) & 0x3F));
+      sout.appendC(base64.charAt((outbytes >> 12) & 0x3F));
+      sout.appendC(nPad == 2 ? '=' : base64.charAt((outbytes >> 6) & 0x3F));
+      sout.appendC(nPad >= 1 ? '=' : base64.charAt(outbytes & 0x3F));
+    }
+    return sout;
+  }
+
+  //Note: Just a simple decoder here. Nothing fancy at all
+  //      Because of the 0s in decode64, this is not a VERIFIER
+  //      Rather, it may decode even bad Base64-encoded data
+  //
+  // Bob Hanson 4/2007
+  
+  public static byte[] decodeBase64(String strBase64) {
+    int nBytes = 0;
+    int ch;
+    int pt0 = strBase64.indexOf(";base64,") + 1;
+    if (pt0 > 0)
+      pt0 += 7;
+    char[] chars64 = strBase64.toCharArray();
+    int len64 = chars64.length;
+    if (len64 == 0)
+      return new byte[0];
+    for (int i = len64; --i >= pt0;)
+      nBytes += ((ch = chars64[i] & 0x7F) == 'A' || decode64[ch] > 0 ? 3 : 0);
+    nBytes = nBytes >> 2;
+    byte[] bytes = new byte[nBytes];
+    int offset = 18;
+    for (int i = pt0, pt = 0, b = 0; i < len64; i++) {
+      if (decode64[ch = chars64[i] & 0x7F] > 0 || ch == 'A' || ch == '=') {
+        b |= decode64[ch] << offset;
+        //System.out.println(chars64[i] + " " + decode64[ch] + " " + offset + " " + Integer.toHexString(b));
+        offset -= 6;
+        if (offset < 0) {
+          bytes[pt++] = (byte) ((b & 0xFF0000) >> 16);
+          if (pt < nBytes)
+            bytes[pt++] = (byte) ((b & 0xFF00) >> 8);
+          if (pt < nBytes)
+            bytes[pt++] = (byte) (b & 0xFF);
+          offset = 18;
+          b =  0;
+        }
+      }
+    }
+    return bytes;
+  }    
+}
\ No newline at end of file
diff --git a/src/javajs/util/BinaryDocument.java b/src/javajs/util/BinaryDocument.java
new file mode 100644 (file)
index 0000000..73ec57b
--- /dev/null
@@ -0,0 +1,356 @@
+/* $RCSfile$
+ * $Date: 2006-03-18 15:59:33 -0600 (Sat, 18 Mar 2006) $
+ * $Revision: 4652 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2003-2005  Miguel, Jmol Development, www.jmol.org
+ *
+ * Contact: hansonr@stolaf.edu
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.util;
+
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+import javajs.api.GenericBinaryDocument;
+import javajs.api.GenericOutputChannel;
+
+/* a basic binary file reader (extended by CompoundDocument). 
+ * 
+ * Note that YOU are responsible for determining whether a file
+ * is bigEndian or littleEndian; the default is bigEndian.
+ * 
+ * JavaScript note: readShort() malfunctioned because (short) (xx << 8) 
+ * isn't the same as (int) (xx << 8); same problem in java.io.DataStream
+ * 
+ * 
+ */
+
+public class BinaryDocument extends BC implements GenericBinaryDocument {
+
+  public BinaryDocument() {  
+  }
+
+
+  // called by reflection
+  
+  protected DataInputStream stream;
+  protected boolean isRandom = false;
+  protected boolean isBigEndian = true;
+  protected BufferedInputStream bis;
+  protected long nBytes;  
+  protected GenericOutputChannel out;
+  
+
+  @Override
+  public void close() {
+    if (stream != null)
+      try {
+        stream.close();
+      } catch (IOException e) {
+        // ignore
+      }
+    if (out != null)
+       out.closeChannel();
+  }
+  
+  @Override
+  public BinaryDocument setStream(BufferedInputStream bis, boolean isBigEndian) {
+    this.bis = bis;
+    if (bis != null) {
+      stream = new DataInputStream(bis);
+    }
+    this.isBigEndian = isBigEndian;
+    return this;
+  }
+
+  @Override
+  public BufferedInputStream getInputStream() {
+    return bis;
+  }
+  
+  @Override
+  public void setStreamData(DataInputStream stream, boolean isBigEndian) {
+    if (stream != null)
+      this.stream = stream;
+    this.isBigEndian = isBigEndian;
+  }
+  
+  @Override
+  public void setOutputChannel(GenericOutputChannel out) {
+      this.out = out;
+  }
+
+  public void setRandom(boolean TF) {
+    isRandom = TF;
+    //CANNOT be random for web 
+  }
+  
+  @Override
+  public byte readByte() throws IOException {
+    nBytes++;
+    return ioReadByte();
+  }
+
+  @Override
+  public int readUInt8() throws IOException {
+    nBytes++;
+    int b = stream.readUnsignedByte();
+    if (out != null)
+      out.writeByteAsInt(b);
+    return b;
+  }
+
+  private byte ioReadByte() throws IOException {
+    byte b = stream.readByte();
+    if (out != null)
+      out.writeByteAsInt(b);
+    return b;
+  }
+
+  @Override
+  public byte[] readBytes(int n) throws IOException {
+    byte[] b = new byte[n];
+    readByteArray(b, 0, n);
+    return b;
+  }
+
+  @Override
+  public int readByteArray(byte[] b, int off, int len) throws IOException {
+    int n = ioRead(b, off, len);
+    nBytes += n;
+    return n;
+  }
+
+  private int ioRead(byte[] b, int off, int len) throws IOException {
+    int m = 0;
+    while (len > 0) {
+      int n = stream.read(b, off, len);
+      m += n;
+      if (n > 0 && out != null)
+        out.write(b, off, n);
+      if (n >= len)
+        break;
+      off += n;
+      len -= n;
+    }
+    return m;
+  }
+
+  @Override
+  public String readString(int nChar) throws IOException {
+    byte[] temp = new byte[nChar];
+    int n = readByteArray(temp, 0, nChar);
+    return new String(temp, 0, n, "UTF-8");
+  }
+  
+  @Override
+  public short readShort() throws IOException {
+    nBytes += 2;
+    short n = (isBigEndian ? ioReadShort()
+        : (short) ((ioReadByte() & 0xff) 
+                 | (ioReadByte() & 0xff) << 8));
+    /**
+     * @j2sNative
+     *
+     * return (n > 0x7FFF ? n - 0x10000 : n);
+     */
+    {
+      return n;
+    }
+  }
+
+  private short ioReadShort() throws IOException {
+    short b = stream.readShort();
+    if (out != null)
+      out.writeShort(b);
+    return b;
+  }
+
+
+  @Override
+  public int readIntLE() throws IOException {
+    nBytes += 4;
+    return readLEInt();
+  }
+  
+  @Override
+  public int readInt() throws IOException {
+    nBytes += 4;
+    return (isBigEndian ? ioReadInt() : readLEInt());
+  }
+  
+  private int ioReadInt() throws IOException {
+    int i = stream.readInt();
+    if (out != null)
+      out.writeInt(i);
+    return i;
+  }
+
+  @Override
+  public int swapBytesI(int n) {
+    return (((n >> 24) & 0xff)
+        | ((n >> 16) & 0xff) << 8
+        | ((n >> 8) & 0xff) << 16 
+        | (n & 0xff) << 24);
+  }
+
+  @Override
+  public short swapBytesS(short n) {
+    return (short) ((((n >> 8) & 0xff)
+        | (n & 0xff) << 8));
+  }
+
+  
+  @Override
+  public int readUnsignedShort() throws IOException {
+    nBytes += 2;
+    int a = (ioReadByte() & 0xff);
+    int b = (ioReadByte() & 0xff);
+    return (isBigEndian ? (a << 8) + b : (b << 8) + a);
+  }
+  
+  @Override
+  public long readLong() throws IOException {
+    nBytes += 8;
+    return (isBigEndian ? ioReadLong()
+       : ((((long) ioReadByte()) & 0xff)
+        | (((long) ioReadByte()) & 0xff) << 8
+        | (((long) ioReadByte()) & 0xff) << 16
+        | (((long) ioReadByte()) & 0xff) << 24
+        | (((long) ioReadByte()) & 0xff) << 32
+        | (((long) ioReadByte()) & 0xff) << 40
+        | (((long) ioReadByte()) & 0xff) << 48 
+        | (((long) ioReadByte()) & 0xff) << 54));
+  }
+
+  private long ioReadLong() throws IOException {
+    long b = stream.readLong();
+    if (out != null)
+      out.writeLong(b);
+    return b;
+  }
+
+  private int readLEInt() throws IOException {
+    ioRead(t8, 0, 4);
+    return bytesToInt(t8, 0, false);
+  }
+
+  byte[] t8 = new byte[8];
+  
+  @Override
+  public float readFloat() throws Exception {
+    return intToFloat(readInt());
+  }
+
+  @SuppressWarnings("unused")
+  @Override
+  public double readDouble() throws IOException {
+    /**
+     * 
+     * reading the float equivalent here in JavaScript
+     * 
+     * @j2sNative
+     * 
+     * 
+     */
+    {
+      nBytes += 8;
+      if (true)
+        return (isBigEndian ? ioReadDouble()
+            : Double.longBitsToDouble(readLELong()));
+    }
+    // this is the JavaScript-only part
+    this.readByteArray(this.t8, 0, 8);
+    return bytesToDoubleToFloat(this.t8, 0, this.isBigEndian);
+  }
+  
+  private double ioReadDouble() throws IOException {
+    double d = stream.readDouble();
+    if (out != null)
+      out.writeLong(Double.doubleToRawLongBits(d));
+    return d;
+  }
+
+  private long readLELong() throws IOException {
+    return ((((long) ioReadByte()) & 0xff)
+          | (((long) ioReadByte()) & 0xff) << 8
+          | (((long) ioReadByte()) & 0xff) << 16 
+          | (((long) ioReadByte()) & 0xff) << 24
+          | (((long) ioReadByte()) & 0xff) << 32
+          | (((long) ioReadByte()) & 0xff) << 40
+          | (((long) ioReadByte()) & 0xff) << 48
+          | (((long) ioReadByte()) & 0xff) << 56);
+  }
+
+  @Override
+  public void seek(long offset) {
+    // slower, but all that is available using the applet
+    try {
+      if (offset == nBytes)
+        return;
+      if (offset < nBytes) {
+        stream.reset();
+        if (out != null && nBytes != 0)
+          out.reset();
+        nBytes = 0;
+      } else {
+        offset -= nBytes;
+      }
+      if (out == null) {
+        stream.skipBytes((int)offset);
+      } else {
+        readByteArray(new byte[(int)offset], 0, (int) offset);
+      }
+      nBytes += offset;
+    } catch (IOException e) {
+      System.out.println(e.toString());
+    }
+  }
+
+  @Override
+  public long getPosition() {
+    return nBytes;
+  }
+
+  @Override
+  public SB getAllDataFiles(String binaryFileList, String firstFile) {
+    return null;
+  }
+
+  @Override
+  public void getAllDataMapped(String replace, String string,
+                               Map<String, String> fileData) {
+  }
+
+
+/*  random access -- application only:
+ * 
+    void seekFile(long offset) {
+    try {
+      file.seek(offset);
+    } catch (IOException e) {
+      System.out.println(e.getMessage());
+    }
+  }
+*/
+}
diff --git a/src/javajs/util/CU.java b/src/javajs/util/CU.java
new file mode 100644 (file)
index 0000000..e46287e
--- /dev/null
@@ -0,0 +1,555 @@
+package javajs.util;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import javajs.api.GenericColor;
+
+/**
+ * ColorUtility 
+ * 
+ */
+
+public class CU {
+
+  public static String toRGBHexString(GenericColor c) {
+    int rgb = c.getRGB();    
+    if (rgb == 0)
+      return "000000";
+    String r  = "00" + Integer.toHexString((rgb >> 16) & 0xFF);
+    r = r.substring(r.length() - 2);
+    String g  = "00" + Integer.toHexString((rgb >> 8) & 0xFF);
+    g = g.substring(g.length() - 2);
+    String b  = "00" + Integer.toHexString(rgb & 0xFF);
+    b = b.substring(b.length() - 2);
+    return r + g + b;
+  }
+
+  public static String toCSSString(GenericColor c) {
+    int opacity = c.getOpacity255();
+    if (opacity == 255)
+      return "#" + toRGBHexString(c);
+    int rgb = c.getRGB();
+    return "rgba(" + ((rgb>>16)&0xFF) + "," + ((rgb>>8)&0xff) + "," + (rgb&0xff) + "," + opacity/255f  + ")"; 
+  }
+  
+  private final static String[] colorNames = {
+    "black",                // 000000
+    "pewhite",              // ffffff
+    "pecyan",               // 00ffff
+    "pepurple",             // d020ff
+    "pegreen",              // 00ff00
+    "peblue",               // 6060ff
+    "peviolet",             // ff80c0
+    "pebrown",              // a42028
+    "pepink",               // ffd8d8
+    "peyellow",             // ffff00
+    "pedarkgreen",          // 00c000
+    "peorange",             // ffb000
+    "pelightblue",          // b0b0ff
+    "pedarkcyan",           // 00a0a0
+    "pedarkgray",           // 606060
+  
+    "aliceblue",            // F0F8FF
+    "antiquewhite",         // FAEBD7
+    "aqua",                 // 00FFFF
+    "aquamarine",           // 7FFFD4
+    "azure",                // F0FFFF
+    "beige",                // F5F5DC
+    "bisque",               // FFE4C4
+    "blanchedalmond",       // FFEBCD
+    "blue",                 // 0000FF
+    "blueviolet",           // 8A2BE2
+    "brown",                // A52A2A
+    "burlywood",            // DEB887
+    "cadetblue",            // 5F9EA0
+    "chartreuse",           // 7FFF00
+    "chocolate",            // D2691E
+    "coral",                // FF7F50
+    "cornflowerblue",       // 6495ED
+    "cornsilk",             // FFF8DC
+    "crimson",              // DC143C
+    "cyan",                 // 00FFFF
+    "darkblue",             // 00008B
+    "darkcyan",             // 008B8B
+    "darkgoldenrod",        // B8860B
+    "darkgray",             // A9A9A9
+    "darkgreen",            // 006400
+    "darkkhaki",            // BDB76B
+    "darkmagenta",          // 8B008B
+    "darkolivegreen",       // 556B2F
+    "darkorange",           // FF8C00
+    "darkorchid",           // 9932CC
+    "darkred",              // 8B0000
+    "darksalmon",           // E9967A
+    "darkseagreen",         // 8FBC8F
+    "darkslateblue",        // 483D8B
+    "darkslategray",        // 2F4F4F
+    "darkturquoise",        // 00CED1
+    "darkviolet",           // 9400D3
+    "deeppink",             // FF1493
+    "deepskyblue",          // 00BFFF
+    "dimgray",              // 696969
+    "dodgerblue",           // 1E90FF
+    "firebrick",            // B22222
+    "floralwhite",          // FFFAF0 16775920
+    "forestgreen",          // 228B22
+    "fuchsia",              // FF00FF
+    "gainsboro",            // DCDCDC
+    "ghostwhite",           // F8F8FF
+    "gold",                 // FFD700
+    "goldenrod",            // DAA520
+    "gray",                 // 808080
+    "green",                // 008000
+    "greenyellow",          // ADFF2F
+    "honeydew",             // F0FFF0
+    "hotpink",              // FF69B4
+    "indianred",            // CD5C5C
+    "indigo",               // 4B0082
+    "ivory",                // FFFFF0
+    "khaki",                // F0E68C
+    "lavender",             // E6E6FA
+    "lavenderblush",        // FFF0F5
+    "lawngreen",            // 7CFC00
+    "lemonchiffon",         // FFFACD
+    "lightblue",            // ADD8E6
+    "lightcoral",           // F08080
+    "lightcyan",            // E0FFFF
+    "lightgoldenrodyellow", // FAFAD2
+    "lightgreen",           // 90EE90
+    "lightgrey",            // D3D3D3
+    "lightgray",            // D3D3D3
+    "lightpink",            // FFB6C1
+    "lightsalmon",          // FFA07A
+    "lightseagreen",        // 20B2AA
+    "lightskyblue",         // 87CEFA
+    "lightslategray",       // 778899
+    "lightsteelblue",       // B0C4DE
+    "lightyellow",          // FFFFE0
+    "lime",                 // 00FF00
+    "limegreen",            // 32CD32
+    "linen",                // FAF0E6
+    "magenta",              // FF00FF
+    "maroon",               // 800000
+    "mediumaquamarine",     // 66CDAA
+    "mediumblue",           // 0000CD
+    "mediumorchid",         // BA55D3
+    "mediumpurple",         // 9370DB
+    "mediumseagreen",       // 3CB371
+    "mediumslateblue",      // 7B68EE
+    "mediumspringgreen",    // 00FA9A
+    "mediumturquoise",      // 48D1CC
+    "mediumvioletred",      // C71585
+    "midnightblue",         // 191970
+    "mintcream",            // F5FFFA
+    "mistyrose",            // FFE4E1
+    "moccasin",             // FFE4B5
+    "navajowhite",          // FFDEAD
+    "navy",                 // 000080
+    "oldlace",              // FDF5E6
+    "olive",                // 808000
+    "olivedrab",            // 6B8E23
+    "orange",               // FFA500
+    "orangered",            // FF4500
+    "orchid",               // DA70D6
+    "palegoldenrod",        // EEE8AA
+    "palegreen",            // 98FB98
+    "paleturquoise",        // AFEEEE
+    "palevioletred",        // DB7093
+    "papayawhip",           // FFEFD5
+    "peachpuff",            // FFDAB9
+    "peru",                 // CD853F
+    "pink",                 // FFC0CB
+    "plum",                 // DDA0DD
+    "powderblue",           // B0E0E6
+    "purple",               // 800080
+    "red",                  // FF0000
+    "rosybrown",            // BC8F8F
+    "royalblue",            // 4169E1
+    "saddlebrown",          // 8B4513
+    "salmon",               // FA8072
+    "sandybrown",           // F4A460
+    "seagreen",             // 2E8B57
+    "seashell",             // FFF5EE
+    "sienna",               // A0522D
+    "silver",               // C0C0C0
+    "skyblue",              // 87CEEB
+    "slateblue",            // 6A5ACD
+    "slategray",            // 708090
+    "snow",                 // FFFAFA 16775930
+    "springgreen",          // 00FF7F
+    "steelblue",            // 4682B4
+    "tan",                  // D2B48C
+    "teal",                 // 008080
+    "thistle",              // D8BFD8
+    "tomato",               // FF6347
+    "turquoise",            // 40E0D0
+    "violet",               // EE82EE
+    "wheat",                // F5DEB3
+    "white",                // FFFFFF 16777215
+    "whitesmoke",           // F5F5F5
+    "yellow",               // FFFF00
+    "yellowgreen",          // 9ACD32
+    // plus a few rasmol names/values
+    "bluetint",             // AFD7FF
+    "greenblue",            // 2E8B57
+    "greentint",            // 98FFB3
+    "grey",                 // 808080
+    "gray",                 
+    "pinktint",             // FFABBB
+    "redorange",            // FF4500
+    "yellowtint",           // F6F675
+  };
+  
+  private final static int[] colorArgbs = {
+  //#FFFFC3 hover
+    0xFF000000, // black
+    // plus the PE chain colors
+    0xFFffffff, // pewhite
+    0xFF00ffff, // pecyan
+    0xFFd020ff, // pepurple
+    0xFF00ff00, // pegreen
+    0xFF6060ff, // peblue
+    0xFFff80c0, // peviolet
+    0xFFa42028, // pebrown
+    0xFFffd8d8, // pepink
+    0xFFffff00, // peyellow
+    0xFF00c000, // pedarkgreen
+    0xFFffb000, // peorange
+    0xFFb0b0ff, // pelightblue
+    0xFF00a0a0, // pedarkcyan
+    0xFF606060, // pedarkgray
+    // standard JavaScript
+    0xFFF0F8FF, // aliceblue
+    0xFFFAEBD7, // antiquewhite
+    0xFF00FFFF, // aqua
+    0xFF7FFFD4, // aquamarine
+    0xFFF0FFFF, // azure
+    0xFFF5F5DC, // beige
+    0xFFFFE4C4, // bisque
+    0xFFFFEBCD, // blanchedalmond
+    0xFF0000FF, // blue
+    0xFF8A2BE2, // blueviolet
+    0xFFA52A2A, // brown
+    0xFFDEB887, // burlywood
+    0xFF5F9EA0, // cadetblue
+    0xFF7FFF00, // chartreuse
+    0xFFD2691E, // chocolate
+    0xFFFF7F50, // coral
+    0xFF6495ED, // cornflowerblue
+    0xFFFFF8DC, // cornsilk
+    0xFFDC143C, // crimson
+    0xFF00FFFF, // cyan
+    0xFF00008B, // darkblue
+    0xFF008B8B, // darkcyan
+    0xFFB8860B, // darkgoldenrod
+    0xFFA9A9A9, // darkgray
+    0xFF006400, // darkgreen
+  
+    0xFFBDB76B, // darkkhaki
+    0xFF8B008B, // darkmagenta
+    0xFF556B2F, // darkolivegreen
+    0xFFFF8C00, // darkorange
+    0xFF9932CC, // darkorchid
+    0xFF8B0000, // darkred
+    0xFFE9967A, // darksalmon
+    0xFF8FBC8F, // darkseagreen
+    0xFF483D8B, // darkslateblue
+    0xFF2F4F4F, // darkslategray
+    0xFF00CED1, // darkturquoise
+    0xFF9400D3, // darkviolet
+    0xFFFF1493, // deeppink
+    0xFF00BFFF, // deepskyblue
+    0xFF696969, // dimgray
+    0xFF1E90FF, // dodgerblue
+    0xFFB22222, // firebrick
+    0xFFFFFAF0, // floralwhite
+    0xFF228B22, // forestgreen
+    0xFFFF00FF, // fuchsia
+    0xFFDCDCDC, // gainsboro
+    0xFFF8F8FF, // ghostwhite
+    0xFFFFD700, // gold
+    0xFFDAA520, // goldenrod
+    0xFF808080, // gray
+    0xFF008000, // green
+    0xFFADFF2F, // greenyellow
+    0xFFF0FFF0, // honeydew
+    0xFFFF69B4, // hotpink
+    0xFFCD5C5C, // indianred
+    0xFF4B0082, // indigo
+    0xFFFFFFF0, // ivory
+    0xFFF0E68C, // khaki
+    0xFFE6E6FA, // lavender
+    0xFFFFF0F5, // lavenderblush
+    0xFF7CFC00, // lawngreen
+    0xFFFFFACD, // lemonchiffon
+    0xFFADD8E6, // lightblue
+    0xFFF08080, // lightcoral
+    0xFFE0FFFF, // lightcyan
+    0xFFFAFAD2, // lightgoldenrodyellow
+    0xFF90EE90, // lightgreen
+    0xFFD3D3D3, // lightgrey
+    0xFFD3D3D3, // lightgray
+    0xFFFFB6C1, // lightpink
+    0xFFFFA07A, // lightsalmon
+    0xFF20B2AA, // lightseagreen
+    0xFF87CEFA, // lightskyblue
+    0xFF778899, // lightslategray
+    0xFFB0C4DE, // lightsteelblue
+    0xFFFFFFE0, // lightyellow
+    0xFF00FF00, // lime
+    0xFF32CD32, // limegreen
+    0xFFFAF0E6, // linen
+    0xFFFF00FF, // magenta
+    0xFF800000, // maroon
+    0xFF66CDAA, // mediumaquamarine
+    0xFF0000CD, // mediumblue
+    0xFFBA55D3, // mediumorchid
+    0xFF9370DB, // mediumpurple
+    0xFF3CB371, // mediumseagreen
+    0xFF7B68EE, // mediumslateblue
+    0xFF00FA9A, // mediumspringgreen
+    0xFF48D1CC, // mediumturquoise
+    0xFFC71585, // mediumvioletred
+    0xFF191970, // midnightblue
+    0xFFF5FFFA, // mintcream
+    0xFFFFE4E1, // mistyrose
+    0xFFFFE4B5, // moccasin
+    0xFFFFDEAD, // navajowhite
+    0xFF000080, // navy
+    0xFFFDF5E6, // oldlace
+    0xFF808000, // olive
+    0xFF6B8E23, // olivedrab
+    0xFFFFA500, // orange
+    0xFFFF4500, // orangered
+    0xFFDA70D6, // orchid
+    0xFFEEE8AA, // palegoldenrod
+    0xFF98FB98, // palegreen
+    0xFFAFEEEE, // paleturquoise
+    0xFFDB7093, // palevioletred
+    0xFFFFEFD5, // papayawhip
+    0xFFFFDAB9, // peachpuff
+    0xFFCD853F, // peru
+    0xFFFFC0CB, // pink
+    0xFFDDA0DD, // plum
+    0xFFB0E0E6, // powderblue
+    0xFF800080, // purple
+    0xFFFF0000, // red
+    0xFFBC8F8F, // rosybrown
+    0xFF4169E1, // royalblue
+    0xFF8B4513, // saddlebrown
+    0xFFFA8072, // salmon
+    0xFFF4A460, // sandybrown
+    0xFF2E8B57, // seagreen
+    0xFFFFF5EE, // seashell
+    0xFFA0522D, // sienna
+    0xFFC0C0C0, // silver
+    0xFF87CEEB, // skyblue
+    0xFF6A5ACD, // slateblue
+    0xFF708090, // slategray
+    0xFFFFFAFA, // snow
+    0xFF00FF7F, // springgreen
+    0xFF4682B4, // steelblue
+    0xFFD2B48C, // tan
+    0xFF008080, // teal
+    0xFFD8BFD8, // thistle
+    0xFFFF6347, // tomato
+    0xFF40E0D0, // turquoise
+    0xFFEE82EE, // violet
+    0xFFF5DEB3, // wheat
+    0xFFFFFFFF, // white
+    0xFFF5F5F5, // whitesmoke
+    0xFFFFFF00, // yellow
+    0xFF9ACD32, // yellowgreen
+    // plus a few rasmol names/values
+    0xFFAFD7FF, // bluetint
+    0xFF2E8B57, // greenblue
+    0xFF98FFB3, // greentint
+    0xFF808080, // grey
+    0xFF808080, // gray
+    0xFFFFABBB, // pinktint
+    0xFFFF4500, // redorange
+    0xFFF6F675, // yellowtint
+  };
+
+  private static final Map<String, Integer> mapJavaScriptColors = new Hashtable<String, Integer>();
+
+  static {
+    for (int i = colorNames.length; --i >= 0; )
+      mapJavaScriptColors.put(colorNames[i], Integer.valueOf(colorArgbs[i]));
+  }
+
+  /**
+   * accepts [xRRGGBB] or [0xRRGGBB] or [0xFFRRGGBB] or #RRGGBB or
+   * [red,green,blue] or a valid JavaScript color
+   * 
+   * @param strColor
+   * @return 0 if invalid or integer color
+   */
+  public static int getArgbFromString(String strColor) {
+    int len = 0;
+    if (strColor == null || (len = strColor.length()) == 0)
+      return 0;
+    strColor = strColor.toLowerCase();
+    if (strColor.charAt(0) == '[' && strColor.charAt(len - 1) == ']') {
+      String check;
+      if (strColor.indexOf(",") >= 0) {
+        String[] tokens = PT.split(strColor.substring(1, strColor
+            .length() - 1), ",");
+        if (tokens.length != 3)
+          return 0;
+        float red = PT.parseFloat(tokens[0]);
+        float grn = PT.parseFloat(tokens[1]);
+        float blu = PT.parseFloat(tokens[2]);
+        return colorTriadToFFRGB(red, grn, blu);
+      }
+      switch (len) {
+      case 9:
+        check = "x";
+        break;
+      case 10:
+        check = "0x";
+        break;
+      default:
+        return 0;
+      }
+      if (strColor.indexOf(check) != 1)
+        return 0;
+      strColor = "#" + strColor.substring(len - 7, len - 1);
+      len = 7;
+    }
+    if (len == 7 && strColor.charAt(0) == '#') {
+      try {
+        return PT.parseIntRadix(strColor.substring(1, 7), 16) | 0xFF000000;
+      } catch (Exception e) {
+        return 0;
+      }
+    }
+    Integer boxedArgb = mapJavaScriptColors.get(strColor);
+    return (boxedArgb == null ? 0 : boxedArgb.intValue());
+  }
+
+  public static int colorTriadToFFRGB(float x, float y, float z) {
+    if (x <= 1 && y <= 1 && z <= 1) {
+      if (x > 0)
+        x = x * 256 - 1;
+      if (y > 0)
+        y = y * 256 - 1;
+      if (z > 0)
+        z = z * 256 - 1;
+    }
+    return rgb((int) x, (int) y, (int) z);
+  }
+
+  public static int rgb(int red, int grn, int blu) {
+    return 0xFF000000 | (red << 16) | (grn << 8) | blu;
+  }
+
+  public final static P3 colorPtFromString(String colorName) {
+    return colorPtFromInt(getArgbFromString(colorName), null);
+  }
+
+  public final static P3 colorPtFromInt(int color, P3 pt) {
+    if (pt == null)
+      pt = new P3();
+    pt.set((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF);
+    return pt;
+  }
+
+  public static int colorPtToFFRGB(T3 pt) {
+    return colorTriadToFFRGB(pt.x, pt.y, pt.z);
+  }
+
+  public static void toRGB3f(int c, float[] f) {
+    f[0] = ((c >> 16) & 0xFF) / 255f; // red
+    f[1] = ((c >> 8) & 0xFF) / 255f;
+    f[2] = (c & 0xFF) / 255f;
+  }
+
+  /**
+   * Return a greyscale rgb value 0-FF using NTSC color lightness algorithm
+   *<p>
+   * the alpha component is set to 0xFF. If you want a value in the
+   * range 0-255 then & the result with 0xFF;
+   *
+   * @param rgb the rgb value
+   * @return a grayscale value in the range 0 - 255 decimal
+   */
+  public static int toFFGGGfromRGB(int rgb) {
+    int grey = (((2989 * ((rgb >> 16) & 0xFF)) +
+                (5870 * ((rgb >> 8) & 0xFF)) +
+                (1140 * (rgb & 0xFF)) + 5000) / 10000) & 0xFFFFFF;
+    return rgb(grey, grey, grey);
+  }
+  
+  
+  /**
+   * Convert RGB values to HSL (hue/saturation/lightness)
+   * 
+   * @param rgb
+   *        range 255 255 255
+   * @param doRound
+   *        set to false when just using this for 
+   *        for RGB -- HSL -- HSL' -- RGB' conversion
+   * 
+   * @return the HSL as P3 range 360 100 100
+   * @author hansonr
+   */
+
+  public static P3 rgbToHSL(P3 rgb, boolean doRound) {
+    // adapted from http://tips4java.wordpress.com/2009/07/05/hsl-color/
+    // see http://en.wikipedia.org/wiki/HSL_color_space
+    float r = rgb.x / 255;
+    float g = rgb.y / 255;
+    float b = rgb.z / 255;
+    float min = Math.min(r, Math.min(g, b));
+    float max = Math.max(r, Math.max(g, b));
+
+    //  lightness is just p * 50
+
+    float p = (max + min);
+    float q = (max - min);
+
+    float h = (60 * ((q == 0 ? 0 : max == r ? ((g - b) / q + 6)
+        : max == g ? (b - r) / q + 2 : (r - g) / q + 4))) % 360;
+
+    float s = q / (q == 0 ? 1 : p <= 1 ? p : 2 - p);
+
+    // we round to tenths for HSL so that we can  return enough
+    // precision to get back 1-255 in RGB
+    return (doRound ? P3.new3(Math.round(h*10)/10f, Math.round(s * 1000)/10f,
+        Math.round(p * 500)/10f) : P3.new3(h, s * 100, p * 50));
+  }
+
+  /**
+   * Convert HSL (hue/saturation/luninance) values to RGB
+   *
+   * @param hsl in the range 360, 100, 100
+   * @return the RGB as P3 range 0 to 255
+   * @author hansonr
+   */
+  public static P3 hslToRGB(P3 hsl) {
+    // adapted from http://tips4java.wordpress.com/2009/07/05/hsl-color/
+    // see http://en.wikipedia.org/wiki/HSL_color_space
+    
+    // highly condensed
+    
+    float h = Math.max(0,  Math.min(360, hsl.x)) / 60;
+    float s = Math.max(0,  Math.min(100, hsl.y)) / 100;
+    float l = Math.max(0,  Math.min(100, hsl.z)) / 100;
+
+    float p = l - (l < 0.5 ? l : 1 - l) * s;    
+    float q = 2 * (l - p); 
+        
+    float r = toRGB(p, q, h + 2);
+    float g = toRGB(p, q, h);
+    float b = toRGB(p, q, h - 2);
+    return P3.new3(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255));
+  }
+
+  private static float toRGB(float p, float q, float h) {
+    return ((h = (h + (h < 0 ? 6 : h > 6 ? -6 : 0))) < 1 ? p + q * h
+        : h < 3 ? p + q : h < 4 ? p + q * (4 - h) : p);
+  }
+
+}
diff --git a/src/javajs/util/CifDataParser.java b/src/javajs/util/CifDataParser.java
new file mode 100644 (file)
index 0000000..51e969c
--- /dev/null
@@ -0,0 +1,905 @@
+package javajs.util;
+
+import java.io.BufferedReader;
+
+import java.util.Hashtable;
+
+import java.util.Map;
+
+import javajs.api.GenericCifDataParser;
+import javajs.api.GenericLineReader;
+
+
+// BH 11/21/16 -- adds support for array grouping [...] - used in 2016-format magCIF files
+
+/**
+*
+* A CIF 1.0 tokenizer class for dealing with quoted strings in CIF files.
+* 
+* Subclassed by org.jmol.adapters.readers.cif.Cif2DataParser
+* 
+* Greek letters implemented in Jmol 13.3.9 and only for 
+* titles and space groups. All other mark ups ignored.
+* 
+*<p>
+* regarding the treatment of single quotes vs. primes in
+* cif file, PMR wrote:
+*</p>
+*<p>
+*   * There is a formal grammar for CIF
+* (see http://www.iucr.org/iucr-top/cif/index.html)
+* which confirms this. The textual explanation is
+*<p />
+*<p>
+* 14. Matching single or double quote characters (' or ") may
+* be used to bound a string representing a non-simple data value
+* provided the string does not extend over more than one line.
+*<p />
+*<p>
+* 15. Because data values are invariably separated from other
+* tokens in the file by white space, such a quote-delimited
+* character string may contain instances of the character used
+* to delimit the string provided they are not followed by white
+* space. For example, the data item
+*<code>
+*  _example  'a dog's life'
+*</code>
+* is legal; the data value is a dog's life.
+*</p>
+*<p>
+* [PMR - the terminating character(s) are quote+whitespace.
+* That would mean that:
+*<code>
+*  _example 'Jones' life'
+*</code>
+* would be an error
+*</p>
+*<p>
+* The CIF format was developed in that late 1980's under the aegis of the
+* International Union of Crystallography (I am a consultant to the COMCIFs 
+* committee). It was ratified by the Union and there have been several 
+* workshops. mmCIF is an extension of CIF which includes a relational 
+* structure. The formal publications are:
+*</p>
+*<p>
+* Hall, S. R. (1991). "The STAR File: A New Format for Electronic Data 
+* Transfer and Archiving", J. Chem. Inform. Comp. Sci., 31, 326-333.
+* Hall, S. R., Allen, F. H. and Brown, I. D. (1991). "The Crystallographic
+* Information File (CIF): A New Standard Archive File for Crystallography",
+* Acta Cryst., A47, 655-685.
+* Hall, S.R. & Spadaccini, N. (1994). "The STAR File: Detailed 
+* Specifications," J. Chem. Info. Comp. Sci., 34, 505-508.
+*</p>
+*/
+
+public class CifDataParser implements GenericCifDataParser {
+
+  protected int getVersion() {
+    return 1;
+  }
+
+  /**
+   * The maximum number of columns (data keys) passed to the parser or found in the file
+   * for a given loop_ or category.subkey listing.
+   * 
+   */
+  public static final int KEY_MAX = 100;
+
+  private GenericLineReader reader;
+  private BufferedReader br;
+
+  /**
+   * from buffered reader
+   */
+  protected String line;
+  
+  /**
+   * working string (buffer)
+   * 
+   */
+  protected String str;
+  
+  /**
+   * pointer to current character on str
+   */
+  protected int ich;
+  
+  /**
+   * length of str
+   * 
+   */
+  protected int cch;
+  
+  /**
+   * whether we are processing an unquoted value or key
+   */
+  protected boolean wasUnquoted;
+  
+  /**
+   * optional token terminator; in CIF 2.0 could be } or ] 
+   */
+  protected char cterm = '\0';
+  
+  /**
+   * string to return for CIF data value . and ?
+   */
+  protected String nullString = "\0";
+
+  /**
+   * A flag to create and return Java objects, not strings.
+   * Used only by Jmol scripting x = getProperty("cifInfo", filename).
+   */
+  protected boolean asObject;
+
+  
+  /**
+   * debugging flag passed from reader; unused
+   * 
+   */
+  protected boolean debugging;
+
+
+  /**
+   * private processing fields
+   * 
+   */
+  private Object strPeeked;
+  private int ichPeeked;
+  private int columnCount;
+  private String[] columnNames;
+  private Object[] columnData = new Object[KEY_MAX];
+  private boolean isLoop;
+  private boolean haveData;
+  
+  /**
+   * comments at the top of a file, including #\#CIF_2.0, for example
+   */
+  private SB fileHeader = new SB(); 
+  private boolean isHeader = true;
+
+
+  /**
+   * Set the string value of what is returned for "." and "?"
+   * 
+   * @param nullString null here returns "." and "?"; default is "\0"
+   * 
+   */
+  public void setNullValue(String nullString) {
+    this.nullString  = nullString;    
+  }
+
+  /**
+   * A global, static map that contains field information. The assumption is that
+   * if we read a set of fields for, say, atom_site, once in a lifetime, then
+   * that should be good forever. Those are static lists. Or should be....
+   */
+  private static Map<String, Integer> htFields = new Hashtable<String, Integer>();
+  
+  ////////////////////////////////////////////////////////////////
+  // special tokenizer class
+  ////////////////////////////////////////////////////////////////
+
+  public CifDataParser() {
+    // for reflection
+  }
+    
+  @Override
+  public Object getColumnData(int i) {
+    return columnData[i];
+  }
+
+  @Override
+  public int getColumnCount() {
+    return columnCount;
+  }
+
+  @Override
+  public String getColumnName(int i) {
+    return columnNames[i];
+  }
+
+  /**
+   * A Chemical Information File data parser.
+   * 
+   * set() should be called immediately upon construction.
+   *  
+   * Two options; one of reader or br should be null, or reader will be
+   * ignored. Just simpler this way...
+   * 
+   * @param reader  Anything that can deliver a line of text or null
+   * @param br      A standard BufferedReader.
+   * @param debugging 
+   *  
+   */
+  @Override
+  public CifDataParser set(GenericLineReader reader, BufferedReader br, boolean debugging) {
+    this.reader = reader;
+    this.br = br;
+    this.debugging = debugging;
+    return this;
+  }
+
+
+  /**
+   * 
+   * @return commented-out section at the start of a CIF file.
+   * 
+   */
+  @Override
+  public String getFileHeader() {
+    return fileHeader.toString();
+  }
+  
+  
+  /**
+   * Parses all CIF data for a reader defined in the constructor
+   * into a standard Map structure and close the BufferedReader if
+   * it exists. 
+   * 
+   * @return Hashtable of models Vector of Hashtable data
+   */
+  @Override
+  public Map<String, Object> getAllCifData() {
+    line = "";
+    String key;
+    Map<String, Object> data = null, data0 = null;
+    Map<String, Object> allData = new Hashtable<String, Object>();
+    Lst<Map<String, Object>> models = new  Lst<Map<String,Object>>();
+    allData.put("models", models);
+    asObject = (getVersion() >= 2);
+    nullString = null;
+    Lst<Map<String, Object>> saveFrames = new Lst<Map<String, Object>>();
+    try {
+      while ((key = getNextToken()) != null) {
+        if (key.startsWith("global_") || key.startsWith("data_")) {
+          models.addLast(data0 = data = new Hashtable<String, Object>());
+          data.put("name", key);
+          continue;
+        }
+        if (key.startsWith("loop_")) {
+          getAllCifLoopData(data);
+          continue;
+        }
+        if (key.startsWith("save_")) {
+          if (key.equals("save_")) {
+            int n = saveFrames.size();
+            if (n == 0) {
+              System.out.println("CIF ERROR ? save_ without corresponding save_xxxx");
+              data = data0;
+            } else {
+              data = saveFrames.removeItemAt(n - 1);
+            }
+          } else {
+            saveFrames.addLast(data);
+            Map<String, Object> d = data;
+            data = new Hashtable<String, Object>();
+            d.put(key, data);
+          }
+          continue;
+        }
+        if (key.charAt(0) != '_') {
+          System.out.println("CIF ERROR ? should be an underscore: " + key);
+        } else {
+          Object value = (asObject ? getNextTokenObject() : getNextToken());
+          if (value == null) {
+            System.out.println("CIF ERROR ? end of file; data missing: " + key);
+          } else {
+            data.put(fixKey(key), value);
+          }
+        }
+      }
+    } catch (Exception e) {
+      // ?
+    }
+    asObject = false;
+    try {
+      if (br != null)
+        br.close();
+    } catch (Exception e) {
+      // ?
+    }
+    nullString = "\0";
+    return allData;
+  }
+
+  /**
+   * create our own list of keywords and for each one create a list
+   * of data associated with that keyword. For example, a list of all 
+   * x coordinates, then a list of all y coordinates, etc.
+   * 
+   * @param data
+   * @throws Exception
+   */
+  @SuppressWarnings("unchecked")
+  private void getAllCifLoopData(Map<String, Object> data) throws Exception {
+    String key;
+    Lst<String> keyWords = new  Lst<String>();
+    Object o;
+    while ((o = peekToken()) != null && o instanceof String &&  ((String) o).charAt(0) == '_') {
+      key = fixKey((String) getTokenPeeked());
+      keyWords.addLast(key);
+      data.put(key, new  Lst<String>());
+    }
+    columnCount = keyWords.size();
+    if (columnCount == 0)
+      return;
+    isLoop = true;
+    while (getData())
+      for (int i = 0; i < columnCount; i++)
+        ((Lst<Object>)data.get(keyWords.get(i))).addLast(columnData[i]);
+    isLoop = false;
+  }
+
+  @Override
+  public String readLine() {
+    try {
+      line = (reader == null ? br.readLine() : reader.readNextLine());
+      if (line == null)
+        return null;
+      if (isHeader) {
+        if (line.startsWith("#"))
+          fileHeader.append(line).appendC('\n');
+        else
+          isHeader = false;
+      }
+      return line;
+    } catch (Exception e) {
+      return null;
+    }
+  }
+  
+  /**
+   * The work horse; a general reader for loop data. Fills colunnData with
+   * fieldCount fields.
+   * 
+   * @return false if EOF
+   * @throws Exception
+   */
+  @Override
+  public boolean getData() throws Exception {
+    // line is already present, and we leave with the next line to parse
+    if (isLoop) {
+      for (int i = 0; i < columnCount; ++i)
+        if ((columnData[i] = getNextDataToken()) == null)
+          return false;
+    } else if (haveData) {
+      haveData = false;
+    } else {
+      return false;
+    }
+    return (columnCount > 0);
+  }
+
+  /**
+   * 
+   * Skips all associated loop data. (Skips to next control word.)
+   * 
+   * @throws Exception
+   */
+  @Override
+  public String skipLoop(boolean doReport) throws Exception {
+    String str;
+    SB ret = (doReport ? new SB() : null);
+    int n = 0;
+    while ((str = (String) peekToken()) != null && str.charAt(0) == '_') {
+      if (ret != null)
+        ret.append(str).append("\n");
+      getTokenPeeked();
+      n++;
+    }
+    if (n == 0)
+      n = columnCount; // end-of-label-section skip 
+    int m = 0;
+    while ((str = (String) getNextDataToken()) != null) {
+      if (ret == null)
+        continue; 
+      ret.append(str).append(" ");
+      if ((++m % n) == 0)
+        ret.append("\n");
+    }
+    return (ret == null ? null : ret.toString());
+  }
+
+  /**
+   * Get a token as a String value (for the reader)
+   * 
+   * @return the next token of any kind, or null
+   * @throws Exception
+   */
+  @Override
+  public String getNextToken() throws Exception {
+    wasUnquoted = true;
+    return (String) getNextTokenProtected();
+  }
+
+  /**
+   * Get the token as a Java Object
+   * 
+   * @return the next token of any kind, or null
+   * @throws Exception
+   */
+  public Object getNextTokenObject() throws Exception {
+    wasUnquoted = true;
+    return getNextTokenProtected();
+  }
+
+  /**
+   * Just makes sure
+   * @return String from buffer.
+   * @throws Exception
+   */
+  protected Object getNextTokenProtected() throws Exception {
+    return (getNextLine() ? nextStrToken() : null);
+  }
+
+  /**
+   * 
+   * first checks to see if the next token is an unquoted
+   * control code, and if so, returns null 
+   * 
+   * @return next data token or null
+   * @throws Exception
+   */
+  @Override
+  public Object getNextDataToken() throws Exception { 
+    Object o = peekToken();
+    if (o == null)
+      return null;
+    if (wasUnquoted && o instanceof String) {
+      String str = (String) o;
+      if (str.charAt(0) == '_' || str.startsWith("loop_")
+          || str.startsWith("data_")
+          || str.startsWith("save_")
+          || str.startsWith("stop_")
+          || str.startsWith("global_"))
+        return null;
+    }
+    return getTokenPeeked();
+  }
+  
+  /**
+   * Just look at the next token. Saves it for retrieval 
+   * using getTokenPeeked()
+   * 
+   * @return next token or null if EOF
+   * @throws Exception
+   */
+  @Override
+  public Object peekToken() throws Exception {
+    if (!getNextLine())
+      return null;
+    int ich = this.ich;
+    strPeeked = nextStrToken();
+    ichPeeked= this.ich;
+    this.ich = ich;
+    return strPeeked;
+  }
+  
+  /**
+   * grab a new line if necessary and prepare it 
+   * if it starts with ";"
+   * 
+   * @return updated this.str
+   * @throws Exception
+   */
+  private boolean getNextLine() throws Exception {
+    while (!strHasMoreTokens())
+      if (prepareNextLine() == null)
+        return false;
+    return true;
+  }
+
+  /**
+   * 
+   * @return the token last acquired; may be null
+   */
+  @Override
+  public Object getTokenPeeked() {
+    ich = ichPeeked;
+    return strPeeked;
+  }
+  
+  /**
+   * Used especially for data that might be multi-line data that
+   * might have unwanted white space at start or end.
+   * 
+   * @param str
+   * @return str without any leading/trailing white space, and no '\n'
+   */
+  @Override
+  public String fullTrim(String str) {
+    int pt0 = -1;
+    int pt1 = str.length();
+    while (++pt0 < pt1 && PT.isWhitespace(str.charAt(pt0))) {
+    }
+    while (--pt1 > pt0 && PT.isWhitespace(str.charAt(pt1))) {      
+    }
+    return str.substring(pt0, pt1 + 1);
+  }
+
+  private final static String grABC =
+      "ABX\u0394E\u03A6\u0393H"   // ABCDEFGH
+      + "I_K\u039BMNO\u03A0"      // I_KLMNOP
+      + "\u0398P\u03A3TY_\u03A9\u039E\u03A5Z"; // QRSTU_WXYZ
+  private final static String grabc =
+      "\u03B1\u03B2\u03C7\u03A4\u03A5\u03C6\u03B3\u03B7" // abcdefgh
+      + "\u03B9_\u03BA\u03BB\u03BC\u03BD\u03BF\u03C0"    // i_klmnop
+      + "\u03B8\u03C1\u03C3\u03C4\u03C5_\u03C9\u03BE\u03C5\u03B6"; // qrstu_wxyz
+
+  /**
+   * Only translating the basic Greek set here, not all the other stuff. See
+   * http://www.iucr.org/resources/cif/spec/version1.1/semantics#markup
+   * 
+   * @param data
+   * @return cleaned string
+   */
+  @Override
+  public String toUnicode(String data) {
+    int pt;
+    try {
+      while ((pt = data.indexOf('\\')) >= 0) {
+        int c = data.charAt(pt + 1);
+        String ch = (c >= 65 && c <= 90 ? grABC.substring(c - 65, c - 64)
+            : c >= 97 && c <= 122 ? grabc.substring(c - 97, c - 96) : "_");
+        data = data.substring(0, pt) + ch + data.substring(pt + 2);
+      }
+    } catch (Exception e) {
+      // ignore
+    }
+
+    return data;
+  }
+
+  /**
+   * Process a data block, with or without a loop_.
+   * 
+   * Passed an array of field names, this method fills two int[] arrays. The
+   * first, key2col, maps desired key values to actual order of appearance
+   * (column number) in the file; the second, col2key, is a reverse loop-up for
+   * that, mapping column numbers to desired field indices. 
+   * 
+   * When called within a loop_ context, this.columnData will be created but not filled.
+   * 
+   * Alternatively, if fields is null, then this.fieldNames is
+   * filled, in order, with key data, and both key2col and col2key will be
+   * simply 0,1,2,... This array is used in cases such as matrices for which
+   * there are simply too many possibilities to list, and the key name itself
+   * contains information that we need.
+   * 
+   * When not a loop_ context, keys are expected to be in the mmCIF form
+   * category.subkey and will be unique within a data block (see
+   * http://mmcif.wwpdb.org/docs/tutorials/mechanics/pdbx-mmcif-syntax.html).
+   * Keys and data will be read for all data in the same category, filling this.columnData.
+   * 
+   * 
+   * In this way, the calling class does not need to enumerate all possible
+   * category names, but instead can focus on just those of interest.
+   * 
+   * 
+   * @param fields
+   *        list of normalized field names, such as
+   *        "_pdbx_struct_assembly_gen_assembly_id" (with "_" instead of ".")
+   * @param key
+   *        null to indicate a loop_ construct, otherwise the initial category.subkey
+   *        found
+   * @param data
+   *        when not loop_ the initial data read, otherwise ignored
+   * @param key2col
+   *        map of desired keys to actual columns
+   * @param col2key
+   *        map of actual columns to desired keys
+   * @throws Exception
+   */
+  @Override
+  public void parseDataBlockParameters(String[] fields, String key,
+                                 String data, int[] key2col, int[] col2key) throws Exception {
+    isLoop = (key == null);
+    Object o;
+    String s;
+    if (fields == null) {
+      // for reading full list of keys, as for matrices
+      columnNames = new String[KEY_MAX];
+    } else {
+      if (!htFields.containsKey(fields[0]))
+        for (int i = fields.length; --i >= 0;)
+          htFields.put(fields[i], Integer.valueOf(i));
+      for (int i = fields.length; --i >= 0;)
+        key2col[i] = NONE;
+    }
+    columnCount = 0;
+    int pt, i;
+    if (isLoop) {
+      while (true) {
+        o = peekToken();
+        if (o == null) {
+          // we are PREMATURELY done; reset
+          columnCount = 0;
+          break;
+        }
+        // end of the loop is a new token not starting with underscore
+        if (!(o instanceof String) || ((String) o).charAt(0) != '_')
+          break;
+
+        pt = columnCount++;
+        s = fixKey((String) getTokenPeeked());
+        if (fields == null) {
+          // just make a linear model, saving the list
+          columnNames[col2key[pt] = key2col[pt] = pt] = s;
+          continue;
+        }
+        Integer iField = htFields.get(s);
+        i = (iField == null ? NONE : iField.intValue());
+        if ((col2key[pt] = i) != NONE)
+          key2col[i] = pt;
+      }
+    } else {
+      pt = key.indexOf(".");
+      String str0 = (pt < 0 ? key : key.substring(0, pt + 1));
+      while (true) {
+        // end of the loop is a new token not starting with underscore
+        pt = columnCount++;
+        if (key == null) {
+          key = (String) getTokenPeeked();
+          data = getNextToken();
+        }
+        Integer iField = htFields.get(fixKey(key));
+        i = (iField == null ? NONE : iField.intValue());
+        if ((col2key[pt] = i) != NONE) 
+          columnData[key2col[i] = pt] = data;
+        if ((o = peekToken()) == null || !(o instanceof String) ||  !((String) o).startsWith(str0))
+          break;
+        key = null;
+      }
+      haveData = (columnCount > 0);
+    }
+  }
+
+  @Override
+  public String fixKey(String key) {
+    // PRELIMINARY -- BilBao _magnetic
+    // PRELIMINARY -- Jana2006
+    return (
+        key.startsWith("_magnetic") ? key.substring(9) 
+            : key.startsWith("_jana") ? key.substring(5) 
+            : key).replace('.', '_').toLowerCase();
+  }
+
+  //////////////////// private methods ////////////////////
+  
+  
+  /**
+   * sets global str and line to be parsed from the beginning
+   * 
+   * \1 .... \1  indicates an embedded fully escaped data object
+   * 
+   * @param str new data string
+   * @return str
+   */
+  protected String setString(String str) {
+    this.str = line = str;
+    cch = (str == null ? 0 : str.length());
+    ich = 0;
+    return str;
+  }
+
+  /*
+   * http://www.iucr.org/resources/cif/spec/version1.1/cifsyntax
+   * 
+   * 17. The special sequence of end-of-line followed 
+   * immediately by a semicolon in column one (denoted "<eol>;") 
+   * may also be used as a delimiter at the beginning and end 
+   * of a character string comprising a data value. The complete 
+   * bounded string is called a text field, and may be used to 
+   * convey multi-line values. The end-of-line associated with 
+   * the closing semicolon does not form part of the data value. 
+   * Within a multi-line text field, leading white space within 
+   * text lines must be retained as part of the data value; trailing 
+   * white space on a line may however be elided.
+   * 
+   * 18. A text field delimited by the <eol>; digraph may not 
+   * include a semicolon at the start of a line of text as 
+   * part of its value.
+   * 
+   * 20. For example, the data value foo may be expressed 
+   * equivalently as an unquoted string foo, as a quoted 
+   * string 'foo' or as a text field
+   *
+   *;foo
+   *;
+   *
+   * By contrast the value of the text field
+   *
+   *; foo
+   *  bar
+   *;
+   *
+   * is  foo<eol>  bar (where <eol> represents an end-of-line); 
+   * the embedded space characters are significant.
+   * 
+   * 
+   * I (BH) note, however, that we sometimes have:
+   * 
+   * _some_name
+   * ;
+   * the name here
+   * ;
+   * 
+   * so this should actually be
+   * 
+   * ;the name here
+   * ;
+   * 
+   * for this, we use fullTrim();
+   * 
+   */
+  
+  /**
+   * 
+   * sets the string for parsing to be from the next line 
+   * when the token buffer is empty, and if ';' is at the 
+   * beginning of that line, extends the string to include
+   * that full multiline string. Uses \1 to indicate that 
+   * this is a special quotation. 
+   * 
+   * 
+   * 
+   * @return  the next line or null if EOF
+   * @throws Exception
+   */
+  protected String prepareNextLine() throws Exception {
+    setString(readLine());
+    if (line == null || line.length() == 0)
+      return line;
+    if (line.charAt(0) == ';')
+      return preprocessString();
+    if (str.startsWith("###non-st#"))
+        ich = 10;
+    return line;
+ }
+
+  /**
+   * Preprocess the string on a line starting with a semicolon
+   * to produce a string with a \1 ... \1 segment
+   * that will be picked up in the next round
+   *  
+   * @return escaped part with attached extra data
+   * @throws Exception 
+   */
+  protected String preprocessString() throws Exception {
+    return setString(preprocessSemiString());
+  }
+
+  /**
+   * Encapsulate a multi-line ; .... ;  string with \1 ... \1
+   * 
+   * CIF 1.0 and CIF 2.0
+   * 
+   * @return ecapsulated string
+   * @throws Exception 
+   */
+  protected String preprocessSemiString() throws Exception {
+    ich = 1;
+    String str = '\1' + line.substring(1) + '\n';
+    while (readLine() != null) {
+      if (line.startsWith(";")) {
+        // remove trailing <eol> only, and attach rest of next line
+        str = str.substring(0, str.length() - 1)
+          + '\1' + line.substring(1);
+        break;
+      }
+      str += line + '\n';
+    }
+    return str;
+  }
+
+  /**
+   * @return TRUE if there are more tokens in the line buffer
+   * 
+   */
+  private boolean strHasMoreTokens() {
+    if (str == null)
+      return false;
+    char ch = '#';
+    while (ich < cch && ((ch = str.charAt(ich)) == ' ' || ch == '\t'))
+      ++ich;
+    return (ich < cch && ch != '#');
+  }
+
+  /**
+   * assume that hasMoreTokens() has been called and that ich is pointing at a
+   * non-white character. Also sets boolean wasUnQuoted, because we need to know
+   * if we should be checking for a control keyword. 'loop_' is different from
+   * just loop_ without the quotes.
+   * 
+   * @return null if no more tokens, "\0" if '.' or '?', or next token
+   */
+  private Object nextStrToken() {
+    if (ich == cch)
+      return null;
+    char ch = str.charAt(ich);
+    if (isQuote(ch)) {
+      wasUnquoted = false;
+      return getQuotedStringOrObject(ch);
+    }
+    int ichStart = ich;
+    wasUnquoted = true;
+    while (ich < cch && !isTerminator(ch = str.charAt(ich)))
+      ++ich;
+    if (ich == ichStart + 1)
+      if (nullString != null
+          && (str.charAt(ichStart) == '.' || str.charAt(ichStart) == '?'))
+        return nullString;
+    String s = str.substring(ichStart, ich);
+    return unquoted(s);
+  }
+
+  /**
+   * In CIF 2.0, this method turns a String into an Integer or Float
+   * In CIF 1.0 (here) just return the unchanged value.
+   * @param s unquoted string
+   * @return unchanged value
+   */
+  protected Object unquoted(String s) {
+    return s;
+  }
+
+  /**
+   * The token terminator is space or tab in CIF 1.0, 
+   * but it can be quoted strings in CIF 2.0.
+   * 
+   * @param c
+   * @return true if this character is a terminator
+   */
+  protected boolean isTerminator(char c) {
+    return  c == ' ' || c == '\t' || c == cterm ;
+  }
+
+  /**
+   * CIF 1.0 only; we handle various quote types here 
+   * @param ch
+   * @return true if this character is a (starting) quote
+   */
+  protected boolean isQuote(char ch) {
+    switch (ch) {
+    case '\'':
+    case '\"':
+    case '\1':
+      return  true;
+    }
+    return false;
+  }
+
+  /**
+   * CIF 1.0 only. 
+   * 
+   *  
+   * @param ch current character being pointed to
+   * @return a String data object
+   */
+  protected Object getQuotedStringOrObject(char ch) {
+    int ichStart = ich;
+    char chClosingQuote = ch;
+    boolean wasQuote = false;
+    while (++ich < cch) {
+      ch = str.charAt(ich);
+      // CIF 1.0 rules require that the closing ' or ""  be followed by space or tab or EOL
+      if (wasQuote && (ch == ' ' || ch == '\t'))
+        break;
+      wasQuote = (ch == chClosingQuote);
+    }
+    int pt1 = ichStart + 1;
+    int pt2 = ich - 1;
+    if (ich == cch && !wasQuote) {
+      // reached the end of the string without finding closing '
+      // so take the whole thing. Probably a bad CIF file.
+      pt1--;
+      pt2++;
+    } else {
+      // throw away the last white character
+      ++ich; 
+    }
+    return str.substring(pt1, pt2);
+  }
+
+  
+}
\ No newline at end of file
diff --git a/src/javajs/util/CompoundDocDirEntry.java b/src/javajs/util/CompoundDocDirEntry.java
new file mode 100644 (file)
index 0000000..a57f6af
--- /dev/null
@@ -0,0 +1,101 @@
+/* $RCSfile$
+ * $Author$
+ * $Date$
+ * $Revision$
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2011  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+package javajs.util;
+
+
+class CompoundDocDirEntry {
+
+  private final CompoundDocument cd;
+
+  /**
+   * @param compoundDocument
+   */
+  CompoundDocDirEntry(CompoundDocument compoundDocument) {
+    cd = compoundDocument;
+  }
+
+  // 128 bytes
+  //offset 0:
+  byte[] unicodeName64 = new byte[64];
+  short nBytesUnicodeName; // twice the ascii length, including terminating 0
+  byte entryType; // 0 empty; 1 storage; 2 stream; 5 root storage
+  //byte entryColor; // 0 red or 1 black
+  //int DIDchildLeft;
+  //int DIDchildRight;
+  //int DIDstorageRoot;
+  byte[] uniqueID16 = new byte[16];
+  byte[] userflags4 = new byte[4];
+  //long timeStamp1;
+  //long timeStamp2;
+  //offset 116:
+  int SIDfirstSector; // either SAT or SSAT
+  int lenStream;
+  byte[] unused = new byte[8];
+
+  // derived:
+
+  String entryName;
+  boolean isStandard;
+  boolean isEmpty;
+
+  final boolean readData() {
+    try {
+      cd.readByteArray(unicodeName64, 0, 64);
+      nBytesUnicodeName = cd.readShort();
+      entryType = cd.readByte();
+      /*entryColor = */cd.readByte();
+      /*DIDchildLeft = */cd.readInt();
+      /*DIDchildRight = */cd.readInt();
+      /*DIDstorageRoot = */cd.readInt();
+      cd.readByteArray(uniqueID16, 0, 16);
+      cd.readByteArray(userflags4, 0, 4);
+      /*timeStamp1 = */      cd.readByteArray(unused, 0, 8);//cd.readLong();
+      /*timeStamp2 = */      cd.readByteArray(unused, 0, 8);//cd.readLong();
+      //offset 116:
+      SIDfirstSector = cd.readInt();
+      lenStream = cd.readInt();
+      cd.readByteArray(unused, 0, 4);
+    } catch (Exception e) {
+      System.out.println(e.toString());
+      return false;
+    }
+    entryName = "";
+    for (int i = 0; i < nBytesUnicodeName - 2; i += 2)
+      entryName += (char) unicodeName64[i];
+    isStandard = (entryType == 5 || lenStream >= cd.header.minBytesStandardStream);
+    isEmpty = (entryType == 0 || lenStream <= 0);
+    //System.out.println(entryName + " type " + entryType);
+    return true;
+  }
+  
+  @Override
+  public String toString() {
+    return entryName + " " + lenStream;
+  }
+}
\ No newline at end of file
diff --git a/src/javajs/util/CompoundDocHeader.java b/src/javajs/util/CompoundDocHeader.java
new file mode 100644 (file)
index 0000000..72317fe
--- /dev/null
@@ -0,0 +1,115 @@
+/* $RCSfile$
+ * $Author$
+ * $Date$
+ * $Revision$
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2011  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+package javajs.util;
+
+
+class CompoundDocHeader {
+
+  /**
+   * 
+   */
+  private final CompoundDocument cd;
+
+  /**
+   * @param compoundDocument
+   */
+  CompoundDocHeader(CompoundDocument compoundDocument) {
+    cd = compoundDocument;
+  }
+
+  //512 bytes
+  //offset 0:
+  byte[] magicNumbers = new byte[8]; // D0CF11E0A1B11AE1
+  byte[] uniqueID16 = new byte[16];
+  byte revNumber; // 3E = 62
+  //byte unusedb1;
+  byte verNumber; // 3
+  //byte unusedb2;
+  //short byteOrder; // -2 littleEndian
+  short sectorPower; // 2^sectorPower = sector size; 512 = 2^9
+  short shortSectorPower; // 2^shortSectorPower = short sector size; 64 = 2^6
+  byte[] unused = new byte[10];
+  int nSATsectors; // number of sectors for sector allocation table
+  int SID_DIR_start; // sector identifier of start of directory sector
+  //offset 56:
+  int minBytesStandardStream; // less than this (and not DIR) will be "short"
+  int SID_SSAT_start; // start of short sector allocation table (SSAT)
+  int nSSATsectors; // number of sectors allocated to SSAT
+  int SID_MSAT_next; // pointer to next master sector allocation table sector
+  int nAdditionalMATsectors; // number of sectors allocated to more MSAT sectors
+  //offset 76; 436 bytes:      
+  int[] MSAT0 = new int[109]; // beginning of master allocation table 
+
+  /*
+   *  Sector 0 is first sector AFTER this header
+   *  
+   *  If sectorPower = 9, then this allows for 109 PAGES
+   *  of sector allocation tables, with 127 pointers per
+   *  page (plus 1 pointer to the next SAT page), each 
+   *  pointing to a sector of 512 bytes. Thus, with no additional
+   *  MSAT pages, the header allows for 109*128*512 = 7.1 Mb file
+   *  
+   */
+
+  final boolean readData() {
+    try {
+      cd.readByteArray(magicNumbers, 0, 8);
+      if ((magicNumbers[0] & 0xFF) != 0xD0 || (magicNumbers[1] & 0xFF) != 0xCF
+          || (magicNumbers[2] & 0xFF) != 0x11 || (magicNumbers[3] & 0xFF) != 0xE0
+          || (magicNumbers[4] & 0xFF) != 0xA1 || (magicNumbers[5] & 0xFF) != 0xB1
+          || (magicNumbers[6] & 0xFF) != 0x1A || (magicNumbers[7] & 0xFF) != 0xE1)
+        return false;
+      cd.readByteArray(uniqueID16, 0, 16);
+      revNumber = cd.readByte();
+      cd.readByte();
+      verNumber = cd.readByte();
+      cd.readByte();
+      byte b1 = cd.readByte();
+      byte b2 = cd.readByte();
+      cd.isBigEndian = (b1 == -1 && b2 == -2);
+      sectorPower = cd.readShort();
+      shortSectorPower = cd.readShort();
+      cd.readByteArray(unused, 0, 10);
+      nSATsectors = cd.readInt();
+      SID_DIR_start = cd.readInt();
+      cd.readByteArray(unused, 0, 4);
+      minBytesStandardStream = cd.readInt();
+      SID_SSAT_start = cd.readInt();
+      nSSATsectors = cd.readInt();
+      SID_MSAT_next = cd.readInt();
+      nAdditionalMATsectors = cd.readInt();
+      for (int i = 0; i < 109; i++)
+        MSAT0[i] = cd.readInt();
+    } catch (Exception e) {
+      System.out.println(e.toString());
+      return false;
+    }
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/src/javajs/util/CompoundDocument.java b/src/javajs/util/CompoundDocument.java
new file mode 100644 (file)
index 0000000..2e2c242
--- /dev/null
@@ -0,0 +1,395 @@
+/* $RCSfile$
+ * $Author: egonw $
+ * $Date: 2006-03-18 15:59:33 -0600 (Sat, 18 Mar 2006) $
+ * $Revision: 4652 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2003-2005  Miguel, Jmol Development, www.jmol.org
+ *
+ * Contact: hansonr@stolaf.edu
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.util;
+
+
+import java.io.DataInputStream;
+import java.io.BufferedInputStream;
+
+
+import java.util.Map;
+
+import javajs.api.GenericZipTools;
+
+
+
+/* a simple compound document reader. 
+ * 
+ * DIRECTORY STRUCTURE IS NOT REGENERATED
+ * 
+ * See http://sc.openoffice.org/compdocfileformat.pdf
+ * 
+ * random access file info: 
+ * http://java.sun.com/docs/books/tutorial/essential/io/rafs.html
+ * 
+ * SHOOT! random access is only for applications, not applets!
+ * 
+ * With a bit more work, this could be set up to deliver binary files, but
+ * right now I've only implemented it for string-based data. 
+ * 
+ */
+
+public class CompoundDocument extends BinaryDocument{
+
+//  RandomAccessFile file;
+  CompoundDocHeader header = new CompoundDocHeader(this);
+  Lst<CompoundDocDirEntry> directory = new  Lst<CompoundDocDirEntry>();
+  CompoundDocDirEntry rootEntry;
+
+  protected GenericZipTools jzt;
+
+  int[] SAT;
+  int[] SSAT;
+  int sectorSize;
+  int shortSectorSize;
+  int nShortSectorsPerStandardSector;
+  int nIntPerSector;
+  int nDirEntriesperSector;
+
+  // called by reflection
+  
+  public CompoundDocument(){
+    super();
+    this.isBigEndian = true;
+  }
+  
+  public void setDocStream(GenericZipTools jzt, BufferedInputStream bis) {
+    this.jzt = jzt;
+    if (!isRandom) {
+      stream = new DataInputStream(bis);
+    }
+    stream.mark(Integer.MAX_VALUE);
+    if (!readHeader())
+      return;
+    getSectorAllocationTable();
+    getShortSectorAllocationTable();
+    getDirectoryTable();
+  }
+
+  public Lst<CompoundDocDirEntry> getDirectory() {
+    return directory;
+  }
+
+  public String getDirectoryListing(String separator) {
+    SB sb = new SB();
+    for (int i = 0; i < directory.size(); i++) {
+      CompoundDocDirEntry thisEntry = directory.get(i);
+      if (!thisEntry.isEmpty)
+        sb.append(separator).append(thisEntry.entryName)
+        .append("\tlen=").appendI(thisEntry.lenStream)
+        .append("\tSID=").appendI(thisEntry.SIDfirstSector)
+        .append(thisEntry.isStandard ? "\tfileOffset="
+                + getOffset(thisEntry.SIDfirstSector) : "");
+    }
+    return sb.toString();
+  }
+
+  public SB getAllData() {
+    return getAllDataFiles(null, null);
+  }
+
+  /**
+   * reads a compound document directory and saves all data in a Hashtable
+   * so that the files may be organized later in a different order. Also adds
+   * a #Directory_Listing entry.
+   * 
+   * Files are bracketed by BEGIN Directory Entry and END Directory Entry lines, 
+   * similar to ZipUtil.getAllData.
+   * 
+   * @param prefix
+   * @param binaryFileList   |-separated list of files that should be saved
+   *                         as xx xx xx hex byte strings. The directory listing
+   *                         is appended with ":asBinaryString"
+   * @param fileData
+   */
+  @Override
+  public void getAllDataMapped(String prefix, 
+                         String binaryFileList, Map<String, String> fileData) {
+    fileData.put("#Directory_Listing", getDirectoryListing("|"));
+    binaryFileList = "|" + binaryFileList + "|";
+    for (int i = 0; i < directory.size(); i++) {
+      CompoundDocDirEntry thisEntry = directory.get(i);
+      if (!thisEntry.isEmpty && thisEntry.entryType != 5) {
+      String name = thisEntry.entryName;
+        System.out.println("CompoundDocument file " + name);
+        boolean isBinary = (binaryFileList.indexOf("|" + name + "|") >= 0);
+        if (isBinary)
+          name += ":asBinaryString";
+        fileData.put(prefix + "/" + name, appendData(new SB(), name, thisEntry, isBinary).toString());
+      }
+    }
+    close();
+  }
+
+  @Override
+  public SB getAllDataFiles(String binaryFileList, String firstFile) {
+// firstFile is now ignored
+//    if (firstFile != null) {
+//      for (int i = 0; i < directory.size(); i++) {
+//        CompoundDocDirEntry thisEntry = directory.get(i);
+//        if (thisEntry.entryName.equals(firstFile)) {
+//          directory.remove(i);
+//          directory.add(1, thisEntry); // after ROOT_ENTRY
+//          break;
+//        }
+//      }
+//    }
+    SB data = new SB();
+    data.append("Compound Document File Directory: ");
+    data.append(getDirectoryListing("|"));
+    data.append("\n");
+    CompoundDocDirEntry thisEntry;
+    binaryFileList = "|" + binaryFileList + "|";
+    for (int i = 0, n = directory.size(); i < n; i++) {
+      thisEntry = directory.get(i);
+      //System.out.println("CompoundDocument reading " + thisEntry.entryName);
+      String name = thisEntry.entryName;
+      switch (thisEntry.entryType) {
+      case 5: // root
+        break;
+      case 1: // user storage (dir)
+        data.append("NEW Directory ").append(name).append("\n");            
+        break;
+      case 2: // user stream (file)
+        if (name.endsWith(".gz"))
+          name = name.substring(0, name.length() - 3);
+        appendData(data, name, thisEntry, binaryFileList.indexOf("|" + thisEntry.entryName + "|") >= 0);
+        break;
+      }
+    }
+    close();
+    return data;
+  }
+
+  private SB appendData(SB data, String name, CompoundDocDirEntry thisEntry,
+                          boolean isBinary) {
+    data.append("BEGIN Directory Entry ").append(name).append("\n");            
+    data.appendSB(getEntryAsString(thisEntry, isBinary));
+    data.append("\nEND Directory Entry ").append(name).append("\n");
+    return data;
+  }
+
+  public SB getFileAsString(String entryName) {
+    for (int i = 0; i < directory.size(); i++) {
+      CompoundDocDirEntry thisEntry = directory.get(i);
+      if (thisEntry.entryName.equals(entryName))
+        return getEntryAsString(thisEntry, false);
+    }
+    return new SB();
+  }
+
+  private long getOffset(int SID) {
+    return (SID + 1) * sectorSize;
+  }
+
+  private void gotoSector(int SID) {
+    seek(getOffset(SID));
+  }
+
+  private boolean readHeader() {
+    if (!header.readData())
+      return false;
+    sectorSize = 1 << header.sectorPower;
+    shortSectorSize = 1 << header.shortSectorPower;
+    nShortSectorsPerStandardSector = sectorSize / shortSectorSize; // e.g. 512 / 64 = 8
+    nIntPerSector = sectorSize / 4; // e.g. 512 / 4 = 128
+    nDirEntriesperSector = sectorSize / 128; // e.g. 512 / 128 = 4
+//    System.out.println(
+//          "compound document: revNum=" + header.revNumber +
+//          " verNum=" + header.verNumber + " isBigEndian=" + isBigEndian +
+//          " bytes per standard/short sector=" + sectorSize + "/" + shortSectorSize);
+    return true;
+  }
+
+  private void getSectorAllocationTable() {
+    int nSID = 0;
+    int thisSID;
+    SAT = new int[header.nSATsectors * nIntPerSector + 109];
+
+    try {
+      for (int i = 0; i < 109; i++) {
+        thisSID = header.MSAT0[i];
+        if (thisSID < 0)
+          break;
+        gotoSector(thisSID);
+        for (int j = 0; j < nIntPerSector; j++) {
+          SAT[nSID++] = readInt();
+          //Logger.debug(thisSID+"."+j + "/" + (nSID - 1) + " : " + SAT[nSID - 1]);
+        }
+      }
+      int nMaster = header.nAdditionalMATsectors;
+      thisSID = header.SID_MSAT_next;
+      int[] MSAT = new int[nIntPerSector];
+      out: while (nMaster-- > 0 && thisSID >= 0) {
+        // read a page of sector identifiers pointing to SAT sectors
+        gotoSector(thisSID);
+        for (int i = 0; i < nIntPerSector; i++)
+          MSAT[i] = readInt();
+        // read each page of SAT sector identifiers 
+        // last entry is pointer to next master sector allocation table page
+        for (int i = 0; i < nIntPerSector - 1; i++) {
+          thisSID = MSAT[i];
+          if (thisSID < 0)
+            break out;
+          gotoSector(thisSID);
+          for (int j = nIntPerSector; --j >= 0;)
+            SAT[nSID++] = readInt();
+        }
+        thisSID = MSAT[nIntPerSector - 1];
+      }
+    } catch (Exception e) {
+      System.out.println(e.toString());
+    }
+  }
+
+  private void getShortSectorAllocationTable() {
+    int nSSID = 0;
+    int thisSID = header.SID_SSAT_start;
+    int nMax = header.nSSATsectors * nIntPerSector;
+    SSAT = new int[nMax];
+    try {
+      while (thisSID > 0 && nSSID < nMax) {
+        gotoSector(thisSID);
+        for (int j = 0; j < nIntPerSector; j++) {
+          SSAT[nSSID++] = readInt();
+          //System.out.println("short: " + thisSID+"."+j+" SSID=" +(nSSID-1)+" "+SSAT[nSSID-1]);
+        }
+        thisSID = SAT[thisSID];
+      }
+    } catch (Exception e) {
+      System.out.println(e.toString());
+    }
+  }
+
+  private void getDirectoryTable() {
+    int thisSID = header.SID_DIR_start;
+    CompoundDocDirEntry thisEntry;
+    rootEntry = null;
+    try {
+      while (thisSID > 0) {
+        gotoSector(thisSID);
+        for (int j = nDirEntriesperSector; --j >= 0;) {
+          thisEntry = new CompoundDocDirEntry(this);
+          thisEntry.readData();
+          directory.addLast(thisEntry);
+          if (thisEntry.entryType == 5)
+            rootEntry = thisEntry;
+        }
+        thisSID = SAT[thisSID];
+      }
+    } catch (Exception e) {
+      System.out.println(e.toString());
+    }
+//    System.out.println("CompoundDocument directory entry: \n"
+//        + getDirectoryListing("\n"));
+  }
+
+  private SB getEntryAsString(CompoundDocDirEntry thisEntry, boolean asBinaryString) {
+    if(thisEntry.isEmpty)
+      return new SB();
+    //System.out.println(thisEntry.entryName + " " + thisEntry.entryType + " " + thisEntry.lenStream + " " + thisEntry.isStandard + " " + thisEntry.SIDfirstSector);
+    return (thisEntry.isStandard ? getStandardStringData(
+            thisEntry.SIDfirstSector, thisEntry.lenStream, asBinaryString)
+            : getShortStringData(thisEntry.SIDfirstSector, thisEntry.lenStream, asBinaryString));
+  }
+  private SB getStandardStringData(int thisSID, int nBytes,
+                                             boolean asBinaryString) {
+    SB data = new SB();
+    byte[] byteBuf = new byte[sectorSize];
+    ZipData gzipData = new ZipData(nBytes);
+    try {
+      while (thisSID > 0 && nBytes > 0) {
+        gotoSector(thisSID);
+        nBytes = getSectorData(data, byteBuf, sectorSize, nBytes, asBinaryString, gzipData);
+        thisSID = SAT[thisSID];
+      }
+      if (nBytes == -9999)
+        return new SB();
+    } catch (Exception e) {
+      System.out.println(e.toString());
+    }
+    if (gzipData.isEnabled)
+      gzipData.addTo(jzt, data);
+    return data;
+  }
+
+  private int getSectorData(SB data, byte[] byteBuf,
+                            int nSectorBytes, int nBytes, 
+                            boolean asBinaryString, ZipData gzipData)
+      throws Exception {
+    readByteArray(byteBuf, 0, byteBuf.length);
+    int n = gzipData.addBytes(byteBuf, nSectorBytes, nBytes);
+    if (n >= 0)
+      return n;
+    if (asBinaryString) {
+      for (int i = 0; i < nSectorBytes; i++) {
+        data.append(Integer.toHexString(byteBuf[i] & 0xFF)).appendC(' ');
+        if (--nBytes < 1)
+          break;
+      }
+    } else {
+      for (int i = 0; i < nSectorBytes; i++) {
+        if (byteBuf[i] == 0)
+          return -9999; // don't allow binary data
+        data.appendC((char) byteBuf[i]);
+        if (--nBytes < 1)
+          break;
+      }
+    }
+    return nBytes;
+  }
+
+  private SB getShortStringData(int shortSID, int nBytes, boolean asBinaryString) {
+    SB data = new SB();
+    if (rootEntry == null)
+      return data;
+    int thisSID = rootEntry.SIDfirstSector;
+    int ptShort = 0;
+    byte[] byteBuf = new byte[shortSectorSize];
+    ZipData gzipData = new ZipData(nBytes);
+    try {
+      //System.out.println("CD shortSID=" + shortSID);
+      // point to correct short data sector, 512/64 = 4 per page
+      while (thisSID >= 0 && shortSID >= 0 && nBytes > 0) {
+        while (shortSID - ptShort >= nShortSectorsPerStandardSector) {
+          ptShort += nShortSectorsPerStandardSector;
+          thisSID = SAT[thisSID];
+        }
+        seek(getOffset(thisSID) + (shortSID - ptShort) * shortSectorSize);
+        nBytes = getSectorData(data, byteBuf, shortSectorSize, nBytes, asBinaryString, gzipData);
+        shortSID = SSAT[shortSID];
+        //System.out.println("CD shortSID=" + shortSID);
+      }
+    } catch (Exception e) {
+      System.out.println(data.toString());
+      System.out.println("reader error in CompoundDocument " + e.toString());
+    }
+    if (gzipData.isEnabled)
+      gzipData.addTo(jzt, data);
+    return data;
+  }  
+}
diff --git a/src/javajs/util/DF.java b/src/javajs/util/DF.java
new file mode 100644 (file)
index 0000000..b5c8717
--- /dev/null
@@ -0,0 +1,174 @@
+/* $RCSfile$
+ * $Author: hansonr $
+ * $Date: 2007-04-26 16:57:51 -0500 (Thu, 26 Apr 2007) $
+ * $Revision: 7502 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package javajs.util;
+
+/**
+ * created to remove ambiguities and make a simpler DecimalFormat
+ */
+public class DF {
+
+  private final static String[] formattingStrings = { "0", "0.0", "0.00",
+      "0.000", "0.0000", "0.00000", "0.000000", "0.0000000", "0.00000000",
+      "0.000000000" };
+  private final static String zeros = "0000000000000000000000000000000000000000";
+
+  private final static float[] formatAdds = { 0.5f, 0.05f, 0.005f, 0.0005f,
+      0.00005f, 0.000005f, 0.0000005f, 0.00000005f, 0.000000005f, 0.0000000005f };
+
+  private final static Boolean[] useNumberLocalization = new Boolean[] { Boolean.TRUE };
+
+  public static void setUseNumberLocalization(boolean TF) {
+    useNumberLocalization[0] = (TF ? Boolean.TRUE : Boolean.FALSE);
+  }
+
+  public static String formatDecimalDbl(double value, int decimalDigits) {
+    if (decimalDigits == Integer.MAX_VALUE 
+        || value == Double.NEGATIVE_INFINITY
+        || value == Double.POSITIVE_INFINITY 
+        || Double.isNaN(value))
+      return "" + value;
+    return DF.formatDecimal((float) value, decimalDigits);
+  }
+
+  /**
+   * a simple alternative to DecimalFormat (which Java2Script does not have
+   * and which is quite too complex for our use here.)
+   * 
+   * @param value
+   * @param decimalDigits
+   * @return  formatted decimal
+   */
+  public static String formatDecimal(float value, int decimalDigits) {
+    if (decimalDigits == Integer.MAX_VALUE 
+        || value == Float.NEGATIVE_INFINITY || value == Float.POSITIVE_INFINITY || Float.isNaN(value))
+      return "" + value;
+    int n;
+    if (decimalDigits < 0) {
+      decimalDigits = -decimalDigits;
+      if (decimalDigits > formattingStrings.length)
+        decimalDigits = formattingStrings.length;
+      if (value == 0)
+        return formattingStrings[decimalDigits - 1] + "E+0";
+      //scientific notation
+      n = 0;
+      double d;
+      if (Math.abs(value) < 1) {
+        n = 10;
+        d = value * 1e-10;
+      } else {
+        n = -10;
+        d = value * 1e10;
+      }
+      String s = ("" + d).toUpperCase();
+      int i = s.indexOf("E");
+      n = PT.parseInt(s.substring(i + 1)) + n;
+      String sf;
+      if (i < 0) {
+        sf = "" + value;
+      } else {
+        float f = PT.parseFloat(s.substring(0, i));
+        if (f == 10 || f == -10) {
+          //d = 9.99999997465; n = -6 --> 10.00000E-5
+          f /= 10;
+          n += (n < 0 ? 1 : -1);          
+        }
+        sf = formatDecimal(f, decimalDigits - 1);
+      }
+      return sf  + "E" + (n >= 0 ? "+" : "") + n;
+    }
+  
+    if (decimalDigits >= formattingStrings.length)
+      decimalDigits = formattingStrings.length - 1;
+    String s1 = ("" + value).toUpperCase();
+    int pt = s1.indexOf(".");
+    if (pt < 0) // specifically JavaScript "-2" not "-2.0"
+      return s1 + formattingStrings[decimalDigits].substring(1);
+    boolean isNeg = s1.startsWith("-");
+    if (isNeg) {
+      s1 = s1.substring(1);
+      pt--;
+    }
+    int pt1 = s1.indexOf("E-");
+    if (pt1 > 0) {
+      n = PT.parseInt(s1.substring(pt1 + 1));
+      // 3.567E-2
+      // 0.03567
+      s1 = "0." + zeros.substring(0, -n - 1) + s1.substring(0, 1) + s1.substring(2, pt1);
+      pt = 1; 
+    }
+  
+    pt1 = s1.indexOf("E");
+    // 3.5678E+3
+    // 3567.800000000
+    // 1.234E10 %3.8f -> 12340000000.00000000
+    if (pt1 > 0) {
+      n = PT.parseInt(s1.substring(pt1 + 1));
+      s1 = s1.substring(0, 1) + s1.substring(2, pt1) + zeros;
+      s1 = s1.substring(0, n + 1) + "." + s1.substring(n + 1);
+      pt = s1.indexOf(".");
+    } 
+    // "234.345667  len == 10; pt = 3
+    // "  0.0 "  decimalDigits = 1
+    
+    int len = s1.length();
+    int pt2 = decimalDigits + pt + 1;
+    if (pt2 < len && s1.charAt(pt2) >= '5') {
+      return formatDecimal(
+          value + (isNeg ? -1 : 1) * formatAdds[decimalDigits], decimalDigits);
+    }
+  
+    SB sb = SB.newS(s1.substring(0, (decimalDigits == 0 ? pt
+        : ++pt)));
+    for (int i = 0; i < decimalDigits; i++, pt++) {
+      if (pt < len)
+        sb.appendC(s1.charAt(pt));
+      else
+        sb.appendC('0');
+    }
+    s1 = (isNeg ? "-" : "") + sb;
+    return (Boolean.TRUE.equals(useNumberLocalization[0]) ? s1 : s1.replace(',',
+        '.'));
+  }
+
+  /**
+   * an alternative to DecimalFormat "0.#"
+   * 
+   * @param x
+   * @param precision
+   * @return  formatted number 
+   */
+  public static String formatDecimalTrimmed(double x, int precision) {
+    String str = formatDecimalDbl(x, precision);
+    int m = str.length() - 1;
+    char zero = '0';
+    while (m >= 0 && str.charAt(m) == zero)
+      m--;
+    return str.substring(0, m + 1); // 0.##...
+  }
+
+}
diff --git a/src/javajs/util/DataReader.java b/src/javajs/util/DataReader.java
new file mode 100644 (file)
index 0000000..1eb8ade
--- /dev/null
@@ -0,0 +1,56 @@
+package javajs.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+/**
+ * Just a simple abstract class to join a String reader and a String[]
+ * reader under the same BufferedReader umbrella.
+ * 
+ * Subclassed as StringDataReader, ArrayDataReader, and ListDataReader
+ * 
+ */
+
+public abstract class DataReader extends BufferedReader {
+
+  public abstract DataReader setData(Object data);
+  
+  protected int ptMark;
+
+  public DataReader() {
+    super(new StringReader(""));
+  }
+
+  protected DataReader(Reader in) {
+    super(in);
+  }
+
+  public BufferedReader getBufferedReader() {
+    return this;
+  }
+
+  protected int readBuf(char[] buf, int off, int len) throws IOException {
+    // not used by StringDataReader
+    int nRead = 0;
+    String line = readLine();
+    if (line == null)
+      return 0;
+    int linept = 0;
+    int linelen = line.length();
+    for (int i = off; i < len && linelen >= 0; i++) {
+      if (linept >= linelen) {
+        linept = 0;
+        buf[i] = '\n';
+        line = readLine();
+        linelen = (line == null ? -1 : line.length());
+      } else {
+        buf[i] = line.charAt(linept++);
+      }
+      nRead++;
+    }
+    return nRead;
+  }
+
+}
\ No newline at end of file
diff --git a/src/javajs/util/DebugJS.java b/src/javajs/util/DebugJS.java
new file mode 100644 (file)
index 0000000..ab22558
--- /dev/null
@@ -0,0 +1,28 @@
+package javajs.util;
+
+/**
+ * A class to insert a JavaScript debug statement for Firefox or Chrome debugger
+ * 
+ */
+
+public class DebugJS {
+
+       /**
+        * Insert a JavaScript debug statement
+        * 
+        */
+                public static void _(String msg) {
+               /**
+                * @j2sNative
+                * 
+                * if (Clazz._debugging) {
+                * 
+                * debugger;
+                * 
+                * }
+                * 
+                */
+               {}
+       }
+
+}
diff --git a/src/javajs/util/Eigen.java b/src/javajs/util/Eigen.java
new file mode 100644 (file)
index 0000000..52736c0
--- /dev/null
@@ -0,0 +1,1063 @@
+/* $RCSfile$
+ * $Author: egonw $
+ * $Date: 2005-11-10 09:52:44f -0600 (Thu, 10 Nov 2005) $
+ * $Revision: 4255 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2003-2005  Miguel, Jmol Development, www.jmol.org
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package javajs.util;
+
+import javajs.api.EigenInterface;
+
+
+/**
+ * Eigenvalues and eigenvectors of a real matrix.
+ * See javajs.api.EigenInterface() as well.
+ * 
+ * adapted by Bob Hanson from http://math.nist.gov/javanumerics/jama/ (public
+ * domain); adding quaternion superimposition capability; removing
+ * nonsymmetric reduction to Hessenberg form, which we do not need in Jmol.
+ * 
+ * Output is as a set of double[n] columns, but for the EigenInterface
+ * we return them as V3[3] and float[3] (or double[3]) values.
+ * 
+ * Eigenvalues and eigenvectors are sorted from smallest to largest eigenvalue.
+ * 
+ * <P>
+ * If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is diagonal
+ * and the eigenvector matrix V is orthogonal. I.e. A =
+ * V.times(D.times(V.transpose())) and V.times(V.transpose()) equals the
+ * identity matrix.
+ * <P>
+ * If A is not symmetric, then the eigenvalue matrix D is block diagonal with
+ * the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, lambda +
+ * i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The columns of V represent
+ * the eigenvectors in the sense that A*V = V*D, i.e. A.times(V) equals
+ * V.times(D). The matrix V may be badly conditioned, or even singular, so the
+ * validity of the equation A = V*D*inverse(V) depends upon V.cond().
+ **/
+
+public class Eigen implements EigenInterface {
+
+  /* ------------------------
+  Public Methods
+  * ------------------------ */
+
+  public Eigen() {}
+  
+  public Eigen set(int n) {
+    this.n = n;
+    V = new double[n][n];
+    d = new double[n];
+    e = new double[n];
+    return this;
+  }
+
+  @Override
+  public Eigen setM(double[][] m) {
+    set(m.length);
+    calc(m);
+    return this;
+  }
+
+  /**
+   * return values sorted from smallest to largest value.
+   */
+  @Override
+  public double[] getEigenvalues() {
+    return d;
+  }
+
+  /**
+   * Specifically for 3x3 systems, returns eigenVectors as V3[3]
+   * and values as float[3]; sorted from smallest to largest value.
+   * 
+   * @param eigenVectors  returned vectors
+   * @param eigenValues   returned values
+   * 
+   */
+  @Override
+  public void fillFloatArrays(V3[] eigenVectors, float[] eigenValues) {
+    for (int i = 0; i < 3; i++) {
+      if (eigenVectors != null) {
+        if (eigenVectors[i] == null)
+          eigenVectors[i] = new V3();
+        eigenVectors[i].set((float) V[0][i], (float) V[1][i], (float) V[2][i]);
+      }
+      if (eigenValues != null)
+        eigenValues[i] = (float) d[i];
+    }
+  }
+
+  /**
+   * Transpose V and turn into floats; sorted from smallest to largest value.
+   * 
+   * @return ROWS of eigenvectors f[0], f[1], f[2], etc.
+   */
+  @Override
+  public float[][] getEigenvectorsFloatTransposed() {
+    float[][] f = new float[n][n];
+    for (int i = n; --i >= 0;)
+      for (int j = n; --j >= 0;)
+        f[j][i] = (float) V[i][j];
+    return f;
+  }
+
+
+  /**
+   * Check for symmetry, then construct the eigenvalue decomposition
+   * 
+   * @param A
+   *        Square matrix
+   */
+
+  public void calc(double[][] A) {
+
+    /* Jmol only has need of symmetric solutions 
+     * 
+    issymmetric = true;
+    
+    for (int j = 0; (j < n) & issymmetric; j++) {
+      for (int i = 0; (i < n) & issymmetric; i++) {
+        issymmetric = (A[i][j] == A[j][i]);
+      }
+    }
+
+    if (issymmetric) {
+     */
+    for (int i = 0; i < n; i++) {
+      for (int j = 0; j < n; j++) {
+        V[i][j] = A[i][j];
+      }
+    }
+
+    // Tridiagonalize.
+    tred2();
+
+    // Diagonalize.
+    tql2();
+    /*
+      } else {
+        H = new double[n][n];
+        ort = new double[n];
+
+        for (int j = 0; j < n; j++) {
+          for (int i = 0; i < n; i++) {
+            H[i][j] = A[i][j];
+          }
+        }
+
+        // Reduce to Hessenberg form.
+        orthes();
+
+        // Reduce Hessenberg to real Schur form.
+        hqr2();
+      }
+    */
+
+  }
+
+  /**
+   * Return the real parts of the eigenvalues
+   * 
+   * @return real(diag(D))
+   */
+
+  public double[] getRealEigenvalues() {
+    return d;
+  }
+
+  /**
+   * Return the imaginary parts of the eigenvalues
+   * 
+   * @return imag(diag(D))
+   */
+
+  public double[] getImagEigenvalues() {
+    return e;
+  }
+
+  /* ------------------------
+     Class variables
+   * ------------------------ */
+
+  /**
+   * Row and column dimension (square matrix).
+   * 
+   * @serial matrix dimension.
+   */
+  private int n = 3;
+
+  /**
+   * Symmetry flag.
+   * 
+   * @serial internal symmetry flag.
+   */
+  //private boolean issymmetric = true;
+
+  /**
+   * Arrays for internal storage of eigenvalues.
+   * 
+   * @serial internal storage of eigenvalues.
+   */
+  private double[] d, e;
+
+  /**
+   * Array for internal storage of eigenvectors.
+   * 
+   * @serial internal storage of eigenvectors.
+   */
+  private double[][] V;
+
+  /**
+   * Array for internal storage of nonsymmetric Hessenberg form.
+   * 
+   * @serial internal storage of nonsymmetric Hessenberg form.
+   */
+  //private double[][] H;
+
+  /**
+   * Working storage for nonsymmetric algorithm.
+   * 
+   * @serial working storage for nonsymmetric algorithm.
+   */
+  //private double[] ort;
+
+  /* ------------------------
+     Private Methods
+   * ------------------------ */
+
+  // Symmetric Householder reduction to tridiagonal form.
+
+  private void tred2() {
+
+    //  This is derived from the Algol procedures tred2 by
+    //  Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
+    //  Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
+    //  Fortran subroutine in EISPACK.
+
+    for (int j = 0; j < n; j++) {
+      d[j] = V[n - 1][j];
+    }
+
+    // Householder reduction to tridiagonal form.
+
+    for (int i = n - 1; i > 0; i--) {
+
+      // Scale to avoid under/overflow.
+
+      double scale = 0.0;
+      double h = 0.0;
+      for (int k = 0; k < i; k++) {
+        scale = scale + Math.abs(d[k]);
+      }
+      if (scale == 0.0) {
+        e[i] = d[i - 1];
+        for (int j = 0; j < i; j++) {
+          d[j] = V[i - 1][j];
+          V[i][j] = 0.0;
+          V[j][i] = 0.0;
+        }
+      } else {
+
+        // Generate Householder vector.
+
+        for (int k = 0; k < i; k++) {
+          d[k] /= scale;
+          h += d[k] * d[k];
+        }
+        double f = d[i - 1];
+        double g = Math.sqrt(h);
+        if (f > 0) {
+          g = -g;
+        }
+        e[i] = scale * g;
+        h = h - f * g;
+        d[i - 1] = f - g;
+        for (int j = 0; j < i; j++) {
+          e[j] = 0.0;
+        }
+
+        // Apply similarity transformation to remaining columns.
+
+        for (int j = 0; j < i; j++) {
+          f = d[j];
+          V[j][i] = f;
+          g = e[j] + V[j][j] * f;
+          for (int k = j + 1; k <= i - 1; k++) {
+            g += V[k][j] * d[k];
+            e[k] += V[k][j] * f;
+          }
+          e[j] = g;
+        }
+        f = 0.0;
+        for (int j = 0; j < i; j++) {
+          e[j] /= h;
+          f += e[j] * d[j];
+        }
+        double hh = f / (h + h);
+        for (int j = 0; j < i; j++) {
+          e[j] -= hh * d[j];
+        }
+        for (int j = 0; j < i; j++) {
+          f = d[j];
+          g = e[j];
+          for (int k = j; k <= i - 1; k++) {
+            V[k][j] -= (f * e[k] + g * d[k]);
+          }
+          d[j] = V[i - 1][j];
+          V[i][j] = 0.0;
+        }
+      }
+      d[i] = h;
+    }
+
+    // Accumulate transformations.
+
+    for (int i = 0; i < n - 1; i++) {
+      V[n - 1][i] = V[i][i];
+      V[i][i] = 1.0;
+      double h = d[i + 1];
+      if (h != 0.0) {
+        for (int k = 0; k <= i; k++) {
+          d[k] = V[k][i + 1] / h;
+        }
+        for (int j = 0; j <= i; j++) {
+          double g = 0.0;
+          for (int k = 0; k <= i; k++) {
+            g += V[k][i + 1] * V[k][j];
+          }
+          for (int k = 0; k <= i; k++) {
+            V[k][j] -= g * d[k];
+          }
+        }
+      }
+      for (int k = 0; k <= i; k++) {
+        V[k][i + 1] = 0.0;
+      }
+    }
+    for (int j = 0; j < n; j++) {
+      d[j] = V[n - 1][j];
+      V[n - 1][j] = 0.0;
+    }
+    V[n - 1][n - 1] = 1.0;
+    e[0] = 0.0;
+  }
+
+  // Symmetric tridiagonal QL algorithm.
+
+  private void tql2() {
+
+    //  This is derived from the Algol procedures tql2, by
+    //  Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
+    //  Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
+    //  Fortran subroutine in EISPACK.
+
+    for (int i = 1; i < n; i++) {
+      e[i - 1] = e[i];
+    }
+    e[n - 1] = 0.0;
+
+    double f = 0.0;
+    double tst1 = 0.0;
+    double eps = Math.pow(2.0, -52.0);
+    for (int l = 0; l < n; l++) {
+
+      // Find small subdiagonal element
+
+      tst1 = Math.max(tst1, Math.abs(d[l]) + Math.abs(e[l]));
+      int m = l;
+      while (m < n) {
+        if (Math.abs(e[m]) <= eps * tst1) {
+          break;
+        }
+        m++;
+      }
+
+      // If m == l, d[l] is an eigenvalue,
+      // otherwise, iterate.
+
+      if (m > l) {
+        int iter = 0;
+        do {
+          iter = iter + 1; // (Could check iteration count here.)
+
+          // Compute implicit shift
+
+          double g = d[l];
+          double p = (d[l + 1] - g) / (2.0 * e[l]);
+          double r = hypot(p, 1.0);
+          if (p < 0) {
+            r = -r;
+          }
+          d[l] = e[l] / (p + r);
+          d[l + 1] = e[l] * (p + r);
+          double dl1 = d[l + 1];
+          double h = g - d[l];
+          for (int i = l + 2; i < n; i++) {
+            d[i] -= h;
+          }
+          f = f + h;
+
+          // Implicit QL transformation.
+
+          p = d[m];
+          double c = 1.0;
+          double c2 = c;
+          double c3 = c;
+          double el1 = e[l + 1];
+          double s = 0.0;
+          double s2 = 0.0;
+          for (int i = m - 1; i >= l; i--) {
+            c3 = c2;
+            c2 = c;
+            s2 = s;
+            g = c * e[i];
+            h = c * p;
+            r = hypot(p, e[i]);
+            e[i + 1] = s * r;
+            s = e[i] / r;
+            c = p / r;
+            p = c * d[i] - s * g;
+            d[i + 1] = h + s * (c * g + s * d[i]);
+
+            // Accumulate transformation.
+
+            for (int k = 0; k < n; k++) {
+              h = V[k][i + 1];
+              V[k][i + 1] = s * V[k][i] + c * h;
+              V[k][i] = c * V[k][i] - s * h;
+            }
+          }
+          p = -s * s2 * c3 * el1 * e[l] / dl1;
+          e[l] = s * p;
+          d[l] = c * p;
+
+          // Check for convergence.
+
+        } while (Math.abs(e[l]) > eps * tst1);
+      }
+      d[l] = d[l] + f;
+      e[l] = 0.0;
+    }
+
+    // Sort eigenvalues and corresponding vectors.
+
+    for (int i = 0; i < n - 1; i++) {
+      int k = i;
+      double p = d[i];
+      for (int j = i + 1; j < n; j++) {
+        if (d[j] < p) {
+          k = j;
+          p = d[j];
+        }
+      }
+      if (k != i) {
+        d[k] = d[i];
+        d[i] = p;
+        for (int j = 0; j < n; j++) {
+          p = V[j][i];
+          V[j][i] = V[j][k];
+          V[j][k] = p;
+        }
+      }
+    }
+  }
+
+  private static double hypot(double a, double b) {
+
+    // sqrt(a^2 + b^2) without under/overflow. 
+
+    double r;
+    if (Math.abs(a) > Math.abs(b)) {
+      r = b / a;
+      r = Math.abs(a) * Math.sqrt(1 + r * r);
+    } else if (b != 0) {
+      r = a / b;
+      r = Math.abs(b) * Math.sqrt(1 + r * r);
+    } else {
+      r = 0.0;
+    }
+    return r;
+  }
+
+  // Nonsymmetric reduction to Hessenberg form.
+
+  /*
+  private void orthes() {
+
+    //  This is derived from the Algol procedures orthes and ortran,
+    //  by Martin and Wilkinson, Handbook for Auto. Comp.,
+    //  Vol.ii-Linear Algebra, and the corresponding
+    //  Fortran subroutines in EISPACK.
+
+    int low = 0;
+    int high = n - 1;
+
+    for (int m = low + 1; m <= high - 1; m++) {
+
+      // Scale column.
+
+      double scale = 0.0;
+      for (int i = m; i <= high; i++) {
+        scale = scale + Math.abs(H[i][m - 1]);
+      }
+      if (scale != 0.0) {
+
+        // Compute Householder transformation.
+
+        double h = 0.0;
+        for (int i = high; i >= m; i--) {
+          ort[i] = H[i][m - 1] / scale;
+          h += ort[i] * ort[i];
+        }
+        double g = Math.sqrt(h);
+        if (ort[m] > 0) {
+          g = -g;
+        }
+        h = h - ort[m] * g;
+        ort[m] = ort[m] - g;
+
+        // Apply Householder similarity transformation
+        // H = (I-u*u'/h)*H*(I-u*u')/h)
+
+        for (int j = m; j < n; j++) {
+          double f = 0.0;
+          for (int i = high; i >= m; i--) {
+            f += ort[i] * H[i][j];
+          }
+          f = f / h;
+          for (int i = m; i <= high; i++) {
+            H[i][j] -= f * ort[i];
+          }
+        }
+
+        for (int i = 0; i <= high; i++) {
+          double f = 0.0;
+          for (int j = high; j >= m; j--) {
+            f += ort[j] * H[i][j];
+          }
+          f = f / h;
+          for (int j = m; j <= high; j++) {
+            H[i][j] -= f * ort[j];
+          }
+        }
+        ort[m] = scale * ort[m];
+        H[m][m - 1] = scale * g;
+      }
+    }
+
+    // Accumulate transformations (Algol's ortran).
+
+    for (int i = 0; i < n; i++) {
+      for (int j = 0; j < n; j++) {
+        V[i][j] = (i == j ? 1.0 : 0.0);
+      }
+    }
+
+    for (int m = high - 1; m >= low + 1; m--) {
+      if (H[m][m - 1] != 0.0) {
+        for (int i = m + 1; i <= high; i++) {
+          ort[i] = H[i][m - 1];
+        }
+        for (int j = m; j <= high; j++) {
+          double g = 0.0;
+          for (int i = m; i <= high; i++) {
+            g += ort[i] * V[i][j];
+          }
+          // Double division avoids possible underflow
+          g = (g / ort[m]) / H[m][m - 1];
+          for (int i = m; i <= high; i++) {
+            V[i][j] += g * ort[i];
+          }
+        }
+      }
+    }
+  }
+
+  // Complex scalar division.
+
+  private transient double cdivr, cdivi;
+
+  private void cdiv(double xr, double xi, double yr, double yi) {
+    double r, d;
+    if (Math.abs(yr) > Math.abs(yi)) {
+      r = yi / yr;
+      d = yr + r * yi;
+      cdivr = (xr + r * xi) / d;
+      cdivi = (xi - r * xr) / d;
+    } else {
+      r = yr / yi;
+      d = yi + r * yr;
+      cdivr = (r * xr + xi) / d;
+      cdivi = (r * xi - xr) / d;
+    }
+  }
+
+  // Nonsymmetric reduction from Hessenberg to real Schur form.
+
+  private void hqr2() {
+
+    //  This is derived from the Algol procedure hqr2,
+    //  by Martin and Wilkinson, Handbook for Auto. Comp.,
+    //  Vol.ii-Linear Algebra, and the corresponding
+    //  Fortran subroutine in EISPACK.
+
+    // Initialize
+
+    int nn = this.n;
+    int n = nn - 1;
+    int low = 0;
+    int high = nn - 1;
+    double eps = Math.pow(2.0, -52.0);
+    double exshift = 0.0;
+    double p = 0, q = 0, r = 0, s = 0, z = 0, t, w, x, y;
+
+    // Store roots isolated by balanc and compute matrix norm
+
+    double norm = 0.0;
+    for (int i = 0; i < nn; i++) {
+      if (i < low || i > high) {
+        d[i] = H[i][i];
+        e[i] = 0.0;
+      }
+      for (int j = Math.max(i - 1, 0); j < nn; j++) {
+        norm = norm + Math.abs(H[i][j]);
+      }
+    }
+
+    // Outer loop over eigenvalue index
+
+    int iter = 0;
+    while (n >= low) {
+
+      // Look for single small sub-diagonal element
+
+      int l = n;
+      while (l > low) {
+        s = Math.abs(H[l - 1][l - 1]) + Math.abs(H[l][l]);
+        if (s == 0.0) {
+          s = norm;
+        }
+        if (Math.abs(H[l][l - 1]) < eps * s) {
+          break;
+        }
+        l--;
+      }
+
+      // Check for convergence
+      // One root found
+
+      if (l == n) {
+        H[n][n] = H[n][n] + exshift;
+        d[n] = H[n][n];
+        e[n] = 0.0;
+        n--;
+        iter = 0;
+
+        // Two roots found
+
+      } else if (l == n - 1) {
+        w = H[n][n - 1] * H[n - 1][n];
+        p = (H[n - 1][n - 1] - H[n][n]) / 2.0;
+        q = p * p + w;
+        z = Math.sqrt(Math.abs(q));
+        H[n][n] = H[n][n] + exshift;
+        H[n - 1][n - 1] = H[n - 1][n - 1] + exshift;
+        x = H[n][n];
+
+        // Real pair
+
+        if (q >= 0) {
+          if (p >= 0) {
+            z = p + z;
+          } else {
+            z = p - z;
+          }
+          d[n - 1] = x + z;
+          d[n] = d[n - 1];
+          if (z != 0.0) {
+            d[n] = x - w / z;
+          }
+          e[n - 1] = 0.0;
+          e[n] = 0.0;
+          x = H[n][n - 1];
+          s = Math.abs(x) + Math.abs(z);
+          p = x / s;
+          q = z / s;
+          r = Math.sqrt(p * p + q * q);
+          p = p / r;
+          q = q / r;
+
+          // Row modification
+
+          for (int j = n - 1; j < nn; j++) {
+            z = H[n - 1][j];
+            H[n - 1][j] = q * z + p * H[n][j];
+            H[n][j] = q * H[n][j] - p * z;
+          }
+
+          // Column modification
+
+          for (int i = 0; i <= n; i++) {
+            z = H[i][n - 1];
+            H[i][n - 1] = q * z + p * H[i][n];
+            H[i][n] = q * H[i][n] - p * z;
+          }
+
+          // Accumulate transformations
+
+          for (int i = low; i <= high; i++) {
+            z = V[i][n - 1];
+            V[i][n - 1] = q * z + p * V[i][n];
+            V[i][n] = q * V[i][n] - p * z;
+          }
+
+          // Complex pair
+
+        } else {
+          d[n - 1] = x + p;
+          d[n] = x + p;
+          e[n - 1] = z;
+          e[n] = -z;
+        }
+        n = n - 2;
+        iter = 0;
+
+        // No convergence yet
+
+      } else {
+
+        // Form shift
+
+        x = H[n][n];
+        y = 0.0;
+        w = 0.0;
+        if (l < n) {
+          y = H[n - 1][n - 1];
+          w = H[n][n - 1] * H[n - 1][n];
+        }
+
+        // Wilkinson's original ad hoc shift
+
+        if (iter == 10) {
+          exshift += x;
+          for (int i = low; i <= n; i++) {
+            H[i][i] -= x;
+          }
+          s = Math.abs(H[n][n - 1]) + Math.abs(H[n - 1][n - 2]);
+          x = y = 0.75 * s;
+          w = -0.4375 * s * s;
+        }
+
+        // MATLAB's new ad hoc shift
+
+        if (iter == 30) {
+          s = (y - x) / 2.0;
+          s = s * s + w;
+          if (s > 0) {
+            s = Math.sqrt(s);
+            if (y < x) {
+              s = -s;
+            }
+            s = x - w / ((y - x) / 2.0 + s);
+            for (int i = low; i <= n; i++) {
+              H[i][i] -= s;
+            }
+            exshift += s;
+            x = y = w = 0.964;
+          }
+        }
+
+        iter = iter + 1; // (Could check iteration count here.)
+
+        // Look for two consecutive small sub-diagonal elements
+
+        int m = n - 2;
+        while (m >= l) {
+          z = H[m][m];
+          r = x - z;
+          s = y - z;
+          p = (r * s - w) / H[m + 1][m] + H[m][m + 1];
+          q = H[m + 1][m + 1] - z - r - s;
+          r = H[m + 2][m + 1];
+          s = Math.abs(p) + Math.abs(q) + Math.abs(r);
+          p = p / s;
+          q = q / s;
+          r = r / s;
+          if (m == l) {
+            break;
+          }
+          if (Math.abs(H[m][m - 1]) * (Math.abs(q) + Math.abs(r)) < eps
+              * (Math.abs(p) * (Math.abs(H[m - 1][m - 1]) + Math.abs(z) + Math
+                  .abs(H[m + 1][m + 1])))) {
+            break;
+          }
+          m--;
+        }
+
+        for (int i = m + 2; i <= n; i++) {
+          H[i][i - 2] = 0.0;
+          if (i > m + 2) {
+            H[i][i - 3] = 0.0;
+          }
+        }
+
+        // Double QR step involving rows l:n and columns m:n
+
+        for (int k = m; k <= n - 1; k++) {
+          boolean notlast = (k != n - 1);
+          if (k != m) {
+            p = H[k][k - 1];
+            q = H[k + 1][k - 1];
+            r = (notlast ? H[k + 2][k - 1] : 0.0);
+            x = Math.abs(p) + Math.abs(q) + Math.abs(r);
+            if (x != 0.0) {
+              p = p / x;
+              q = q / x;
+              r = r / x;
+            }
+          }
+          if (x == 0.0) {
+            break;
+          }
+          s = Math.sqrt(p * p + q * q + r * r);
+          if (p < 0) {
+            s = -s;
+          }
+          if (s != 0) {
+            if (k != m) {
+              H[k][k - 1] = -s * x;
+            } else if (l != m) {
+              H[k][k - 1] = -H[k][k - 1];
+            }
+            p = p + s;
+            x = p / s;
+            y = q / s;
+            z = r / s;
+            q = q / p;
+            r = r / p;
+
+            // Row modification
+
+            for (int j = k; j < nn; j++) {
+              p = H[k][j] + q * H[k + 1][j];
+              if (notlast) {
+                p = p + r * H[k + 2][j];
+                H[k + 2][j] = H[k + 2][j] - p * z;
+              }
+              H[k][j] = H[k][j] - p * x;
+              H[k + 1][j] = H[k + 1][j] - p * y;
+            }
+
+            // Column modification
+
+            for (int i = 0; i <= Math.min(n, k + 3); i++) {
+              p = x * H[i][k] + y * H[i][k + 1];
+              if (notlast) {
+                p = p + z * H[i][k + 2];
+                H[i][k + 2] = H[i][k + 2] - p * r;
+              }
+              H[i][k] = H[i][k] - p;
+              H[i][k + 1] = H[i][k + 1] - p * q;
+            }
+
+            // Accumulate transformations
+
+            for (int i = low; i <= high; i++) {
+              p = x * V[i][k] + y * V[i][k + 1];
+              if (notlast) {
+                p = p + z * V[i][k + 2];
+                V[i][k + 2] = V[i][k + 2] - p * r;
+              }
+              V[i][k] = V[i][k] - p;
+              V[i][k + 1] = V[i][k + 1] - p * q;
+            }
+          } // (s != 0)
+        } // k loop
+      } // check convergence
+    } // while (n >= low)
+
+    // Backsubstitute to find vectors of upper triangular form
+
+    if (norm == 0.0) {
+      return;
+    }
+
+    for (n = nn - 1; n >= 0; n--) {
+      p = d[n];
+      q = e[n];
+
+      // Real vector
+
+      if (q == 0) {
+        int l = n;
+        H[n][n] = 1.0;
+        for (int i = n - 1; i >= 0; i--) {
+          w = H[i][i] - p;
+          r = 0.0;
+          for (int j = l; j <= n; j++) {
+            r = r + H[i][j] * H[j][n];
+          }
+          if (e[i] < 0.0) {
+            z = w;
+            s = r;
+          } else {
+            l = i;
+            if (e[i] == 0.0) {
+              if (w != 0.0) {
+                H[i][n] = -r / w;
+              } else {
+                H[i][n] = -r / (eps * norm);
+              }
+
+              // Solve real equations
+
+            } else {
+              x = H[i][i + 1];
+              y = H[i + 1][i];
+              q = (d[i] - p) * (d[i] - p) + e[i] * e[i];
+              t = (x * s - z * r) / q;
+              H[i][n] = t;
+              if (Math.abs(x) > Math.abs(z)) {
+                H[i + 1][n] = (-r - w * t) / x;
+              } else {
+                H[i + 1][n] = (-s - y * t) / z;
+              }
+            }
+
+            // Overflow control
+
+            t = Math.abs(H[i][n]);
+            if ((eps * t) * t > 1) {
+              for (int j = i; j <= n; j++) {
+                H[j][n] = H[j][n] / t;
+              }
+            }
+          }
+        }
+
+        // Complex vector
+
+      } else if (q < 0) {
+        int l = n - 1;
+
+        // Last vector component imaginary so matrix is triangular
+
+        if (Math.abs(H[n][n - 1]) > Math.abs(H[n - 1][n])) {
+          H[n - 1][n - 1] = q / H[n][n - 1];
+          H[n - 1][n] = -(H[n][n] - p) / H[n][n - 1];
+        } else {
+          cdiv(0.0, -H[n - 1][n], H[n - 1][n - 1] - p, q);
+          H[n - 1][n - 1] = cdivr;
+          H[n - 1][n] = cdivi;
+        }
+        H[n][n - 1] = 0.0;
+        H[n][n] = 1.0;
+        for (int i = n - 2; i >= 0; i--) {
+          double ra, sa, vr, vi;
+          ra = 0.0;
+          sa = 0.0;
+          for (int j = l; j <= n; j++) {
+            ra = ra + H[i][j] * H[j][n - 1];
+            sa = sa + H[i][j] * H[j][n];
+          }
+          w = H[i][i] - p;
+
+          if (e[i] < 0.0) {
+            z = w;
+            r = ra;
+            s = sa;
+          } else {
+            l = i;
+            if (e[i] == 0) {
+              cdiv(-ra, -sa, w, q);
+              H[i][n - 1] = cdivr;
+              H[i][n] = cdivi;
+            } else {
+
+              // Solve complex equations
+
+              x = H[i][i + 1];
+              y = H[i + 1][i];
+              vr = (d[i] - p) * (d[i] - p) + e[i] * e[i] - q * q;
+              vi = (d[i] - p) * 2.0 * q;
+              if (vr == 0.0 & vi == 0.0) {
+                vr = eps
+                    * norm
+                    * (Math.abs(w) + Math.abs(q) + Math.abs(x) + Math.abs(y) + Math
+                        .abs(z));
+              }
+              cdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi);
+              H[i][n - 1] = cdivr;
+              H[i][n] = cdivi;
+              if (Math.abs(x) > (Math.abs(z) + Math.abs(q))) {
+                H[i + 1][n - 1] = (-ra - w * H[i][n - 1] + q * H[i][n]) / x;
+                H[i + 1][n] = (-sa - w * H[i][n] - q * H[i][n - 1]) / x;
+              } else {
+                cdiv(-r - y * H[i][n - 1], -s - y * H[i][n], z, q);
+                H[i + 1][n - 1] = cdivr;
+                H[i + 1][n] = cdivi;
+              }
+            }
+
+            // Overflow control
+
+            t = Math.max(Math.abs(H[i][n - 1]), Math.abs(H[i][n]));
+            if ((eps * t) * t > 1) {
+              for (int j = i; j <= n; j++) {
+                H[j][n - 1] = H[j][n - 1] / t;
+                H[j][n] = H[j][n] / t;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // Vectors of isolated roots
+
+    for (int i = 0; i < nn; i++) {
+      if (i < low || i > high) {
+        for (int j = i; j < nn; j++) {
+          V[i][j] = H[i][j];
+        }
+      }
+    }
+
+    // Back transformation to get eigenvectors of original matrix
+
+    for (int j = nn - 1; j >= low; j--) {
+      for (int i = low; i <= high; i++) {
+        z = 0.0;
+        for (int k = low; k <= Math.min(j, high); k++) {
+          z = z + V[i][k] * H[k][j];
+        }
+        V[i][j] = z;
+      }
+    }
+  }
+     */
+
+
+}
diff --git a/src/javajs/util/Encoding.java b/src/javajs/util/Encoding.java
new file mode 100644 (file)
index 0000000..8d1a9a7
--- /dev/null
@@ -0,0 +1,33 @@
+/* $RCSfile$
+ * $Author$
+ * $Date$
+ * $Revision$
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2011  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+package javajs.util;
+
+public enum Encoding {
+  NONE, UTF8, UTF_16BE, UTF_16LE, UTF_32BE, UTF_32LE
+}
\ No newline at end of file
diff --git a/src/javajs/util/JSAudioThread.java b/src/javajs/util/JSAudioThread.java
new file mode 100644 (file)
index 0000000..3bcb006
--- /dev/null
@@ -0,0 +1,258 @@
+package javajs.util;   
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.SourceDataLine;
+
+import sun.audio.AudioData;
+import sun.audio.AudioDataStream;
+import sun.audio.AudioPlayer;
+
+
+/**
+ * The JSAudioThread adds a layer to JSThread that is specific for audio.
+ * This class utilizes JSAudioLine, which is not fully fleshed out.
+ * 
+ * Two very simple interfaces,
+ * 
+ * (new JSAudioThread()).playULawData(byte[] b);
+ * 
+ * and
+ * 
+ * (new JSAudioThread(audioFormat)).playOnce(byte[] b, offset, length);
+ * 
+ * allow straightforward audio production without having to work with
+ * SourceDataLine directly.
+ * 
+ * As a JSThread, this class implements myInit(), isLooping(), myLoop(),
+ * whenDone(), onException(), and doFinally().
+ * 
+ * If the constructor JSAudioThread(JSAudioThreadUser, AudioFormat, byte[]) is
+ * used, then the JSAudioThreadUser must simply implement fillAudioBuffer(),
+ * checkSoundStatus(), and audioThreadExiting() for very simple streaming audio.
+ * JSAudioThread will then take care of all the timing issues.
+ * 
+ * But the implementer is welcome to override any of the JSThread overrides
+ * themselves in order to customize this further.
+ * 
+ * The standard streaming case, then is:
+ * 
+ * audioThread = new JSAudioThread(audioUser, audioFormat, audioByteBuffer);
+ * audioThread.start();
+ * 
+ * where audioUser provides 
+ * 
+ * checkSoundStatus() (called in isLooping()),
+ * 
+ * fillAudioBuffer() (called in myLoop()),
+ * 
+ * and
+ * 
+ * audioThreadExiting() (called in doFinally()).
+ * 
+ * @author Bob Hanson
+ * 
+ */
+public class JSAudioThread extends JSThread {
+       
+       protected Owner owner;
+       protected boolean done;
+       protected int myBufferLength;
+       protected SourceDataLine line;
+       protected int rate, nChannels, bitsPerSample;
+       
+       protected byte[] audioByteBuffer;
+       protected int audioBufferByteLength;
+
+       private AudioFormat audioFormat;
+       private int myBufferOffset;
+       private int playCount;
+       
+       public JSAudioThread(Owner owner, AudioFormat audioFormat, byte[] audioByteBuffer) {
+               this.owner = owner;
+               setFormat(audioFormat);
+               setBuffer(audioByteBuffer);
+       }
+
+       /**
+        * A convenience constructor requiring standard settings of 
+        * signed (for 8-bit) and littleEndian (for 16-bit)
+        *   
+        * @param owner
+        * @param rate
+        * @param bitsPerSample
+        * @param nChannels
+        * @param audioByteBuffer
+        */
+       public JSAudioThread(Owner owner, int rate, int bitsPerSample, int nChannels, byte[] audioByteBuffer) {
+               this.owner = owner;
+               setFormat(new AudioFormat(rate, bitsPerSample, nChannels, true, false));
+               setBuffer(audioByteBuffer);
+       }
+       
+       /**
+        * primarily available for (new JSAudioThread()).playULawData
+        * 
+        */
+       public JSAudioThread() {
+       }
+
+       /**
+        * primarily available for (new JSAudioThread()).playOnce
+        * 
+        */
+       public JSAudioThread(AudioFormat audioFormat) {
+               setFormat(audioFormat);
+       }
+
+       /**
+        * 
+        * A simple 8-bit uLaw data player
+        * 
+        * @param data
+        */
+       public void playULawData(byte[] data) {
+               // this constructor uses default new AudioFormat(ULAW,8000,8,1,1,8000,true)
+               // threading is taken care of by the browser in JavaScript
+               AudioPlayer.player.start(new AudioDataStream(new AudioData(data)));
+       }
+
+
+       /**
+        * Just play once through; no additions
+        * 
+        * @param data
+        */
+       public void playOnce(byte[] data, int offset, int length) {
+               setBuffer(data);
+               myBufferOffset = offset;
+               myBufferLength = length;
+               playCount = 1;
+               start();
+       }
+
+       public void setBuffer(byte[] audioByteBuffer) {
+               this.audioByteBuffer = audioByteBuffer;
+               audioBufferByteLength = audioByteBuffer.length;
+       }
+       
+       public SourceDataLine getLine() {
+               return line;
+       }
+       
+       public AudioFormat getFormat() {
+               return audioFormat;
+       }
+                       
+       public void setFormat(AudioFormat audioFormat) {
+               this.audioFormat = audioFormat;
+               rate = (int) audioFormat.getSampleRate();
+               bitsPerSample = audioFormat.getSampleSizeInBits();
+               nChannels = audioFormat.getChannels();                  
+       }
+
+       public void resetAudio() {
+               if (line == null)
+                       return;
+               line.flush();
+               line.close();
+               line = null;
+       }
+
+       /**
+        * Standard initialization of a SourceDataLine
+        * 
+        */
+       @Override
+       protected boolean myInit() {
+               try {
+                       DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
+                       if (line != null)
+                               line.close();
+                       line = (SourceDataLine) AudioSystem.getLine(info);
+                       line.open(audioFormat, audioBufferByteLength);
+                       line.start();
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       return false;
+               }
+               return true;
+       }
+       
+       @Override
+       protected boolean isLooping() {
+               return !done && (--playCount >= 0 || owner != null && owner.checkSoundStatus());
+       }
+
+       @Override
+       protected boolean myLoop() {
+               if (!done) {
+                       if ((myBufferLength = (owner == null ? myBufferLength : owner.fillAudioBuffer())) <= 0)
+                               return !(done = true);
+                       try {
+                               if (line == null)
+                                       myInit();                                       
+                               line.write(audioByteBuffer, myBufferOffset, myBufferLength);
+                       } catch (Exception e) {
+                               e.printStackTrace();
+                               done = true;
+                       }
+               }
+               return !done;
+       }
+
+       @Override
+       protected void whenDone() {
+               done = true;
+               resetAudio();
+       }
+
+       @Override
+       protected int getDelayMillis() {
+               // about 25% of the actual play time
+       return 1000                                        // ms/sec 
+                       * (myBufferLength  * 8 / bitsPerSample)        // * samples 
+                       / rate                                         // * seconds/sample) 
+                       / nChannels                                    // / number of channels
+                       / 4;                                           // * 25%
+       }
+
+       @Override
+       protected void onException(Exception e) {
+               e.printStackTrace();
+       }
+
+       @Override
+       protected void doFinally() {
+               if (owner != null)
+                       owner.audioThreadExiting();
+       }
+
+       public interface Owner {
+
+               /**
+                * 
+                * @return true if thread should continue; false if not
+                * 
+                */
+               boolean checkSoundStatus();
+
+               /** fill audio buffer
+                * 
+                * @return number of bytes to write to audio line
+                *
+                */
+               int fillAudioBuffer();
+
+               /**
+                *  called from the finally clause when complete 
+                * 
+                */
+               void audioThreadExiting();
+               
+
+       }
+
+}
+
diff --git a/src/javajs/util/JSJSONParser.java b/src/javajs/util/JSJSONParser.java
new file mode 100644 (file)
index 0000000..552270d
--- /dev/null
@@ -0,0 +1,325 @@
+package javajs.util;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+
+/**
+ * a very simple JSON parser for JSON objects that are compatible with JavaScript
+ * A gross simplification of https://github.com/douglascrockford/JSON-java
+ * 
+ * A SUBSET of JSON with similarly to window.JSON.parse():
+ * 
+ * In JavaScript returns "null" for a null value, not null
+ * 
+ *  -- requires quoted strings for keys and values
+ *  
+ *  -- does not allow /xxx/ objects
+ *  
+ *  @author Bob Hanson
+ *  
+ */
+public class JSJSONParser {
+
+  private String str;
+  private int index;
+  private int len;
+  private boolean asHashTable;
+
+  public JSJSONParser () {
+    // for reflection
+  }
+  
+  /**
+   * requires { "key":"value", "key":"value",....}
+   * 
+   * @param str
+   * @param asHashTable TODO
+   * 
+   * @return Map or null
+   */
+  @SuppressWarnings("unchecked")
+  public Map<String, Object> parseMap(String str, boolean asHashTable) {
+    index = 0;
+    this.asHashTable = asHashTable;
+    this.str = str;
+    len = str.length();
+    if (getChar() != '{')
+      return null;
+    returnChar();
+    return (Map<String, Object>) getValue(false);
+  }
+  
+  /**
+   * Could return Integer, Float, Boolean, String, Map<String, Object>, Lst<Object>, or null
+   * 
+   * @param str
+   * @param asHashTable 
+   * @return a object equivalent to the JSON string str
+   * 
+   */
+  public Object parse(String str, boolean asHashTable) {
+    index = 0;
+    this.asHashTable = asHashTable;
+    this.str = str;
+    len = str.length();
+    return getValue(false);
+  }
+
+  private char next() {
+    return (index < len ? str.charAt(index++) : '\0');
+  }
+
+  private void returnChar() {
+    index--;
+  }
+
+  /**
+   * Get the next char in the string, skipping whitespace.
+   * 
+   * @throws JSONException
+   * @return one character, or 0 if there are no more characters.
+   */
+  private char getChar() throws JSONException {
+    for (;;) {
+      char c = next();
+      if (c == 0 || c > ' ') {
+        return c;
+      }
+    }
+  }
+
+  /**
+   * only allowing the following values:
+   * 
+   * {...} object
+   * 
+   * [...] array
+   * 
+   * Integer
+   * 
+   * Float
+   * 
+   * "quoted string"
+   * 
+   * 
+   * @param isKey if we should allow {...} and [...]
+   * @return a subclass of Object
+   * @throws JSONException
+   */
+  private Object getValue(boolean isKey) throws JSONException {
+    int i = index;
+    char c = getChar();
+    switch (c) {
+    case '\0':
+      return null;
+    case '"':
+    case '\'':
+      return getString(c);
+    case '{':
+      if (!isKey)
+        return getObject();
+      c = 0;
+      break;
+    case '[':
+      if (!isKey)
+        return getArray();
+      c = 0;
+      break;
+    default:
+      // standard syntax is assumed; not checking all possible invalid keys
+      // for example, "-" is not allowed in JavaScript, which is what this is for
+      returnChar();
+      while (c >= ' ' && "[,]{:}'\"".indexOf(c) < 0)
+        c = next();
+      returnChar();
+      if (isKey && c != ':')
+        c = 0;
+      break;
+    }
+    if (isKey && c == 0)
+      throw new JSONException("invalid key");
+
+    String string = str.substring(i, index).trim();
+
+    // check for the only valid simple words: true, false, null (lower case)
+    // and in this case, only for 
+
+    if (!isKey) {
+      if (string.equals("true")) {
+        return Boolean.TRUE;
+      }
+      if (string.equals("false")) {
+        return Boolean.FALSE;
+      }
+      if (string.equals("null")) {
+        return (asHashTable ? string : null);
+      }
+    }
+    //  only numbers from here on:
+    c = string.charAt(0);
+    if (c >= '0' && c <= '9' || c == '-')
+      try {
+        if (string.indexOf('.') < 0 && string.indexOf('e') < 0
+            && string.indexOf('E') < 0)
+          return new Integer(string);
+        // not allowing infinity or NaN
+        // using float here because Jmol does not use Double
+        Float d = Float.valueOf(string);
+        if (!d.isInfinite() && !d.isNaN())
+          return d;
+      } catch (Exception e) {
+      }
+    // not a valid number
+    System.out.println("JSON parser cannot parse " + string);
+    throw new JSONException("invalid value");
+  }
+
+  private String getString(char quote) throws JSONException {
+    char c;
+    SB sb = null;
+    int i0 = index;
+    for (;;) {
+      int i1 = index;
+      switch (c = next()) {
+      case '\0':
+      case '\n':
+      case '\r':
+        throw syntaxError("Unterminated string");
+      case '\\':
+        switch (c = next()) {
+        case '"':
+        case '\'':
+        case '\\':
+        case '/':
+          break;
+        case 'b':
+          c = '\b';
+          break;
+        case 't':
+          c = '\t';
+          break;
+        case 'n':
+          c = '\n';
+          break;
+        case 'f':
+          c = '\f';
+          break;
+        case 'r':
+          c = '\r';
+          break;
+        case 'u':
+          int i = index;
+          index += 4;
+          try {
+            c = (char) Integer.parseInt(str.substring(i, index), 16);
+          } catch (Exception e) {
+            throw syntaxError("Substring bounds error");
+          }
+          break;
+        default:
+          throw syntaxError("Illegal escape.");
+        }
+        break;
+      default:
+        if (c == quote)
+          return (sb == null ? str.substring(i0, i1) : sb.toString());
+        break;
+      }
+      if (index > i1 + 1) {
+        if (sb == null) {
+          sb = new SB();
+          sb.append(str.substring(i0, i1));
+        }
+      }
+      if (sb != null)
+        sb.appendC(c);
+    }
+  }
+
+  private Object getObject() {
+    Map<String, Object> map = (asHashTable ? new Hashtable<String, Object>() : new HashMap<String, Object>());
+    String key = null;
+    switch (getChar()) {
+    case '}':
+      return map;
+    case 0:
+      throw new JSONException("invalid object");
+    }
+    returnChar();
+    boolean isKey = false;
+    for (;;) {
+      if ((isKey = !isKey) == true)
+        key = getValue(true).toString();
+      else
+        map.put(key, getValue(false));
+      switch (getChar()) {
+      case '}':
+        return map;
+      case ':':
+        if (isKey)
+          continue;
+        isKey = true;
+        //$FALL-THROUGH$
+      case ',':
+        if (!isKey)
+          continue;
+        //$FALL-THROUGH$
+      default:
+        throw syntaxError("Expected ',' or ':' or '}'");
+      }
+    }
+  }
+
+  private Object getArray() {
+    Lst<Object> l = new Lst<Object>();
+    switch (getChar()) {
+    case ']':
+      return l;
+    case '\0':
+      throw new JSONException("invalid array");
+    }
+    returnChar();
+    boolean isNull = false;
+    for (;;) {
+      if (isNull) {
+        l.addLast(null);
+        isNull = false;
+      } else {
+        l.addLast(getValue(false));
+      }
+      switch (getChar()) {
+      case ',':
+        switch (getChar()) {
+        case ']':
+          // terminal ,
+          return l;
+        case ',':
+          // empty value
+          isNull = true;
+          //$FALL-THROUGH$
+        default:
+          returnChar();
+        }
+        continue;
+      case ']':
+        return l;
+      default:
+        throw syntaxError("Expected ',' or ']'");
+      }
+    }
+  }
+
+  /**
+   * Make a JSONException to signal a syntax error.
+   * 
+   * @param message
+   *        The error message.
+   * @return A JSONException object, suitable for throwing
+   */
+  public JSONException syntaxError(String message) {
+    return new JSONException(message + " for " + str.substring(0, Math.min(index,  len)));
+  }
+
+}
diff --git a/src/javajs/util/JSONException.java b/src/javajs/util/JSONException.java
new file mode 100644 (file)
index 0000000..58af722
--- /dev/null
@@ -0,0 +1,8 @@
+package javajs.util;
+
+public class JSONException extends RuntimeException {
+    public JSONException(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/javajs/util/JSThread.java b/src/javajs/util/JSThread.java
new file mode 100644 (file)
index 0000000..be94c00
--- /dev/null
@@ -0,0 +1,216 @@
+package javajs.util;
+
+import java.awt.Toolkit;
+import java.awt.event.InvocationEvent;
+
+//import javajs.J2SRequireImport;
+import javajs.api.JSFunction;
+
+
+/**
+ * An abstract class that takes care of simple threading in Java or JavaScript.
+ * 
+ * To use it, subclass it and complete the necessary methods.
+ * 
+ * 
+ * There are three states: INIT, LOOP, and DONE.
+ * 
+ * These states are passed into run1
+ * 
+ * 
+ * @author Bob Hanson
+ * 
+ */
+//@J2SRequireImport(swingjs.JSToolkit.class)
+public abstract class JSThread extends Thread implements JSFunction {
+
+       public static final int INIT = 0;
+       public static final int LOOP = 1;
+       public static final int DONE = 2;
+       
+       public static int threadCount = 0;
+
+       protected boolean isJS;
+       
+       public JSThread() {
+               this(null, "JSThread-" + (++threadCount));
+       }
+       
+       public JSThread(String name) {
+               this(null, name);
+       }
+       
+       public JSThread(ThreadGroup group, String name) {
+               super(group, name);
+               /**
+                * @j2sNative
+                * 
+                * this.isJS = true;
+                */
+               {}
+       }
+
+       @Override
+       public void run() {
+               run1(INIT);
+       }
+
+       @Override
+       public synchronized void start() {
+
+               
+               /**
+                * @j2sNative
+                * 
+                *                        Clazz.load("swingjs.JSToolkit").dispatch$O$I$I(this, 1, 0);
+                * 
+                */
+               {
+                       super.start();
+               }
+
+       }
+
+       /**
+        * thread initialization
+        * 
+        * @return false to exit thread before any looping
+        */
+       protected abstract boolean myInit();
+       
+       /**
+        * check for continuing to loop
+        * 
+        * @return true if we are to continue looping
+        */
+       protected abstract boolean isLooping();
+       
+       /**
+        * 
+        * @return false to handle sleepAndReturn yourself
+        */
+       protected abstract boolean myLoop();
+       /**
+        * what to do when the DONE state is reached
+        * 
+        */
+       protected abstract void whenDone();
+       
+       /**
+        * 
+        * @return the sleep time in milliseconds
+        */
+       protected abstract int getDelayMillis();
+       
+       /**
+        * handle an exception -- state will be set to DONE no matter what you do here
+        * 
+        * @param e
+        */
+       protected abstract void onException(Exception e);
+       
+       /**
+        * anything you want done in  try{}catch(}finally().
+        * Note that this method is not fired if we are in JavaScript
+        * mode and the normal return from sleepAndReturn() is taken. 
+        *  
+        */
+       protected abstract void doFinally();
+       
+       /**
+        * a generic method that loops until done, either in Java or JavaScript.
+        * 
+        * In JavaScript it will reenter and continue at the appropriate spot.
+        * 
+        * This method may be overridden if desired.
+        * 
+        * @see org.uwi.SimThread
+        * 
+        * @param state
+        */
+       protected void run1(int state) {
+               boolean executeFinally = true;
+               // called by thisThread.run();
+               try {
+                       while (!interrupted()) {
+                               switch (state) {
+                               case INIT:
+                                       if (!myInit())
+                                               return;
+                                       // initial code here
+                                       state = LOOP;
+                                       continue;
+                               case LOOP:
+                                       // to stop looping, return false from isLooping()
+                                       if (!isLooping()) {
+                                               state = DONE;
+                                               continue;
+                                       }
+                                       // To handle sleepAndReturn yourself, or to skip the
+                                       // sleep when desired, return false from myLoop();
+                                       // Note that doFinally must not be executed in this case.
+                                       // This is because JavaScript will do a return here
+                                       // for every loop, and Java will not.
+                                       if (myLoop() && sleepAndReturn(getDelayMillis(), state)) {
+                                               executeFinally = false;
+                                               return;                                         
+                                       }
+                                       continue;
+                               case DONE:
+                                       whenDone();
+                                       // whatever
+                                       return;
+                               }
+                       }
+               } catch (Exception e) {
+                       onException(e);
+                       state = DONE;
+               } finally {
+                       if (executeFinally)
+                               doFinally();
+               }
+               // normal exit
+       }
+
+       /**
+        * 
+        * @param r2
+        * @param state
+        * @return true if we should interrupt (i.e. JavaScript)
+        * @throws InterruptedException
+        */
+       protected boolean sleepAndReturn(final int delay, final int state)
+                       throws InterruptedException {
+               if (!isJS) {
+                       sleep(delay);
+                       return false;
+               }
+
+               // in JavaScript, we need to do this through the system event queue,
+               // which in JSToolkit takes care of all the "thread" handling.
+
+               final JSThread me = this;
+               Runnable r = new Runnable() {
+                       @Override
+                       public void run() {
+                               me.run1(state);
+                       }
+               };
+               /**
+                * @j2sNative
+                * 
+                *            setTimeout(
+                *              function() {
+                *              java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent$java_awt_AWTEvent(
+                *              Clazz.new_(java.awt.event.InvocationEvent.c$$O$Runnable,[me, r]))}, 
+                *              delay);
+                * 
+                */
+               {
+                       Toolkit.getDefaultToolkit().getSystemEventQueue()
+                                       .postEvent(new InvocationEvent(me, r));
+               }
+               return true;
+       }
+       
+}
diff --git a/src/javajs/util/LimitedLineReader.java b/src/javajs/util/LimitedLineReader.java
new file mode 100644 (file)
index 0000000..e1d973f
--- /dev/null
@@ -0,0 +1,48 @@
+package javajs.util;
+
+import java.io.BufferedReader;
+
+/**
+ *  A simple class to read a designated number of bytes from a 
+ *  file and then return them line by line, skipping lines that
+ *  start with #, and including the \n or \r characters at line ends.
+ *  
+ *  Generally useful for determining what sort of data a file contains.
+ *   
+ */
+public class LimitedLineReader {
+  private char[] buf;
+  private int cchBuf;
+  private int ichCurrent;
+
+  public LimitedLineReader(BufferedReader bufferedReader, int readLimit)
+    throws Exception {  
+    bufferedReader.mark(readLimit + 1);
+    buf = new char[readLimit];
+    cchBuf = Math.max(bufferedReader.read(buf, 0, readLimit), 0);
+    ichCurrent = 0;
+    bufferedReader.reset();
+  }
+
+  public String getHeader(int n) {
+    return (n == 0 ? new String(buf) : new String(buf, 0, Math.min(cchBuf, n)));
+  }
+  
+  public String readLineWithNewline() {
+    while (ichCurrent < cchBuf) {
+      int ichBeginningOfLine = ichCurrent;
+      char ch = 0;
+      while (ichCurrent < cchBuf &&
+             (ch = buf[ichCurrent++]) != '\r' && ch != '\n') {
+      }
+      if (ch == '\r' && ichCurrent < cchBuf && buf[ichCurrent] == '\n')
+        ++ichCurrent;
+      int cchLine = ichCurrent - ichBeginningOfLine;
+      if (buf[ichBeginningOfLine] == '#')
+        continue; // flush comment lines;
+      return new String(buf, ichBeginningOfLine, cchLine);
+    }
+    return "";
+  }
+}
+
diff --git a/src/javajs/util/ListDataReader.java b/src/javajs/util/ListDataReader.java
new file mode 100644 (file)
index 0000000..7814977
--- /dev/null
@@ -0,0 +1,57 @@
+package javajs.util;
+
+import java.io.IOException;
+
+
+
+
+
+/**
+ * 
+ * VectorDataReader subclasses BufferedReader and overrides its
+ * read, readLine, mark, and reset methods so that JmolAdapter 
+ * works with Vector<String> arrays without any further adaptation. 
+ * 
+ */
+
+public class ListDataReader extends DataReader {
+  private Lst<String> data;
+  private int pt;
+  private int len;
+
+  public ListDataReader() {
+    super();
+  }
+  
+  @SuppressWarnings("unchecked")
+  @Override
+  public DataReader setData(Object data) {
+    this.data = (Lst<String>) data;
+    len = this.data.size();
+    return this;
+  }
+
+  @Override
+  public int read(char[] buf, int off, int len) throws IOException {
+    return readBuf(buf, off, len);
+  }
+
+  @Override
+  public String readLine() {
+    return (pt < len ? data.get(pt++) : null);
+  }
+
+  /**
+   * 
+   * @param ptr
+   */
+  public void mark(long ptr) {
+    //ignore ptr.
+    ptMark = pt;
+  }
+
+  @Override
+  public void reset() {
+    pt = ptMark;
+  }
+}
\ No newline at end of file
diff --git a/src/javajs/util/Lst.java b/src/javajs/util/Lst.java
new file mode 100644 (file)
index 0000000..e7fdda5
--- /dev/null
@@ -0,0 +1,56 @@
+/* $RCSfile$
+ * $Author: hansonr $
+ * $Date: 2007-04-26 16:57:51 -0500 (Thu, 26 Apr 2007) $
+ * $Revision: 7502 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package javajs.util;
+
+import java.util.ArrayList;
+
+/**
+ * created to remove ambiguities in add and remove
+ * 
+ * @param <V>
+ */
+@SuppressWarnings("serial")
+public class Lst<V> extends ArrayList<V> {
+
+  public Lst() {
+    super();  
+  }
+  
+  public boolean addLast(V v) {
+      return super.add(v);
+  }
+  
+  public V removeItemAt(int location) {
+      return super.remove(location);
+  }
+
+  public boolean removeObj(Object v) {
+      return super.remove(v);
+  }
+  
+}
diff --git a/src/javajs/util/M3.java b/src/javajs/util/M3.java
new file mode 100644 (file)
index 0000000..88f6f4e
--- /dev/null
@@ -0,0 +1,637 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+import java.io.Serializable;
+
+
+
+/**
+ * A single precision floating point 3 by 3 matrix.
+ * 
+ * @author Kenji hiranabe
+ * 
+ *         additions by Bob Hanson hansonr@stolaf.edu 9/30/2012 for unique
+ *         constructor and method names for the optimization of compiled
+ *         JavaScript using Java2Script
+ *         
+ *         
+ */
+public class M3 extends M34 implements Serializable {
+
+  /**
+   * Constructs and initializes a Matrix3f to all zeros.
+   */
+  public M3() {
+  }
+
+  /**
+   * Constructs and initializes a Matrix3f from the specified 9 element array.
+   * this.m00 =v[0], this.m01=v[1], etc.
+   * 
+   * @param v
+   *        the array of length 9 containing in order
+   * @return m
+   */
+  public static M3 newA9(float[] v) {
+    M3 m = new M3();
+    m.setA(v);
+    return m;
+  }
+  
+  /**
+   * Constructs a new matrix with the same values as the Matrix3f parameter.
+   * 
+   * @param m1
+   *        The source matrix.
+   * @return m
+   */
+  public static M3 newM3(M3 m1) {
+    M3 m = new M3();
+    if (m1 == null) {
+      m.setScale(1);
+      return m;
+    }
+    m.m00 = m1.m00;
+    m.m01 = m1.m01;
+    m.m02 = m1.m02;
+
+    m.m10 = m1.m10;
+    m.m11 = m1.m11;
+    m.m12 = m1.m12;
+
+    m.m20 = m1.m20;
+    m.m21 = m1.m21;
+    m.m22 = m1.m22;
+    return m;
+  }
+
+  /**
+   * Sets this Matrix3f to a scalar * Identity.
+   * @param scale 
+   */
+  public void setScale(float scale) {
+    clear33();
+    m00 = m11 = m22 = scale;
+  }
+
+  /**
+   * Sets the value of this matrix to the double value of the Matrix3f argument.
+   * 
+   * @param m1
+   *        the matrix3f
+   */
+  public void setM3(M34 m1) {
+    setM33(m1);
+  }
+  /**
+   * Sets the values in this Matrix3f equal to the row-major array parameter
+   * (ie, the first four elements of the array will be copied into the first row
+   * of this matrix, etc.).
+   * 
+   * @param m
+   */
+  public void setA(float m[]) {
+    m00 = m[0];
+    m01 = m[1];
+    m02 = m[2];
+    m10 = m[3];
+    m11 = m[4];
+    m12 = m[5];
+    m20 = m[6];
+    m21 = m[7];
+    m22 = m[8];
+  }
+
+  /**
+   * Sets the specified element of this matrix3d to the value provided.
+   * 
+   * @param row
+   *        the row number to be modified (zero indexed)
+   * @param col
+   *        the column number to be modified (zero indexed)
+   * @param v
+   *        the new value
+   */
+  public void setElement(int row, int col, float v) {
+    set33(row, col, v);
+  }
+
+  /**
+   * Retrieves the value at the specified row and column of this matrix.
+   * 
+   * @param row
+   *        the row number to be retrieved (zero indexed)
+   * @param col
+   *        the column number to be retrieved (zero indexed)
+   * @return the value at the indexed element
+   */
+  public float getElement(int row, int col) {
+    return get33(row, col);
+  }
+
+  /**
+   * Sets the specified row of this matrix3d to the three values provided.
+   * 
+   * @param row
+   *        the row number to be modified (zero indexed)
+   * @param x
+   *        the first column element
+   * @param y
+   *        the second column element
+   * @param z
+   *        the third column element
+   */
+  public void setRow(int row, float x, float y, float z) {
+    switch (row) {
+    case 0:
+      m00 = x;
+      m01 = y;
+      m02 = z;
+      return;
+    case 1:
+      m10 = x;
+      m11 = y;
+      m12 = z;
+      return;
+    case 2:
+      m20 = x;
+      m21 = y;
+      m22 = z;
+      return;
+    default:
+      err();
+    }
+  }
+
+  /**
+   * Sets the specified row of this matrix3d to the Vector provided.
+   * 
+   * @param row
+   *        the row number to be modified (zero indexed)
+   * @param v
+   *        the replacement row
+   */
+  public void setRowV(int row, T3 v) {
+    switch (row) {
+    case 0:
+      m00 = v.x;
+      m01 = v.y;
+      m02 = v.z;
+      return;
+    case 1:
+      m10 = v.x;
+      m11 = v.y;
+      m12 = v.z;
+      return;
+    case 2:
+      m20 = v.x;
+      m21 = v.y;
+      m22 = v.z;
+      return;
+    default:
+      err();
+    }
+  }
+
+  /**
+   * Sets the specified row of this matrix3d to the four values provided.
+   * 
+   * @param row
+   *        the row number to be modified (zero indexed)
+   * @param v
+   *        the replacement row
+   */
+  public void setRowA(int row, float v[]) {
+    setRow33(row, v);
+  }
+
+  /**
+   * Copies the matrix values in the specified row into the array parameter.
+   * 
+   * @param row
+   *        the matrix row
+   * @param v
+   *        The array into which the matrix row values will be copied
+   */
+  @Override
+  public void getRow(int row, float v[]) {
+    getRow33(row, v);
+  }
+
+  /**
+   * Sets the specified column of this matrix3d to the three values provided.
+   * 
+   * @param column
+   *        the column number to be modified (zero indexed)
+   * @param x
+   *        the first row element
+   * @param y
+   *        the second row element
+   * @param z
+   *        the third row element
+   */
+  public void setColumn3(int column, float x, float y, float z) {
+    switch (column) {
+    case 0:
+      m00 = x;
+      m10 = y;
+      m20 = z;
+      break;
+    case 1:
+      m01 = x;
+      m11 = y;
+      m21 = z;
+      break;
+    case 2:
+      m02 = x;
+      m12 = y;
+      m22 = z;
+      break;
+    default:
+      err();
+    }
+  }
+
+  /**
+   * Sets the specified column of this matrix3d to the vector provided.
+   * 
+   * @param column
+   *        the column number to be modified (zero indexed)
+   * @param v
+   *        the replacement column
+   */
+  public void setColumnV(int column, T3 v) {
+    switch (column) {
+    case 0:
+      m00 = v.x;
+      m10 = v.y;
+      m20 = v.z;
+      break;
+    case 1:
+      m01 = v.x;
+      m11 = v.y;
+      m21 = v.z;
+      break;
+    case 2:
+      m02 = v.x;
+      m12 = v.y;
+      m22 = v.z;
+      break;
+    default:
+      err();
+    }
+  }
+
+  /**
+   * Copies the matrix values in the specified column into the vector parameter.
+   * 
+   * @param column
+   *        the matrix column
+   * @param v
+   *        The vector into which the matrix row values will be copied
+   */
+  public void getColumnV(int column, T3 v) {
+    switch (column) {
+    case 0:
+      v.x = m00;
+      v.y = m10;
+      v.z = m20;
+      break;
+    case 1:
+      v.x = m01;
+      v.y = m11;
+      v.z = m21;
+      break;
+    case 2:
+      v.x = m02;
+      v.y = m12;
+      v.z = m22;
+      break;
+    default:
+      err();
+    }
+  }
+
+  /**
+   * Sets the specified column of this matrix3d to the four values provided.
+   * 
+   * @param column
+   *        the column number to be modified (zero indexed)
+   * @param v
+   *        the replacement column
+   */
+  public void setColumnA(int column, float v[]) {
+    setColumn33(column, v);
+  }
+
+  /**
+   * Copies the matrix values in the specified column into the array parameter.
+   * 
+   * @param column
+   *        the matrix column
+   * @param v
+   *        The array into which the matrix row values will be copied
+   */
+  public void getColumn(int column, float v[]) {
+    getColumn33(column, v);
+  }
+
+  /**
+   * Sets the value of this matrix to sum of itself and matrix m1.
+   * 
+   * @param m1
+   *        the other matrix
+   */
+  public void add(M3 m1) {
+    add33(m1);
+  }
+
+  /**
+   * Sets the value of this matrix to the matrix difference of itself and matrix
+   * m1 (this = this - m1).
+   * 
+   * @param m1
+   *        the other matrix
+   */
+  public void sub(M3 m1) {
+    sub33(m1);
+  }
+
+  /**
+   * Sets the value of this matrix to its transpose.
+   */
+  public void transpose() {
+    transpose33();
+  }
+
+  /**
+   * Sets the value of this matrix to the transpose of the argument matrix
+   * 
+   * @param m1
+   *        the matrix to be transposed
+   */
+  public void transposeM(M3 m1) {
+    // alias-safe
+    setM33(m1);
+    transpose33();
+  }
+
+  /**
+   * Sets the value of this matrix to the matrix inverse of the passed matrix
+   * m1.
+   * 
+   * @param m1
+   *        the matrix to be inverted
+   */
+  public void invertM(M3 m1) {
+    setM33(m1);
+    invert();
+  }
+
+  /**
+   * Sets the value of this matrix to its inverse.
+   */
+  public void invert() {
+    double s = determinant3();
+    if (s == 0.0)
+      return;
+    s = 1 / s;
+    // alias-safe way.
+    set9(m11 * m22 - m12 * m21, m02 * m21 - m01 * m22, m01 * m12 - m02 * m11,
+        m12 * m20 - m10 * m22, m00 * m22 - m02 * m20, m02 * m10 - m00 * m12,
+        m10 * m21 - m11 * m20, m01 * m20 - m00 * m21, m00 * m11 - m01 * m10);
+    scale((float) s);
+  }
+
+  /**
+   * Sets the value of this matrix to a rotation matrix about the x axis by the
+   * passed angle.
+   * 
+   * @param angle
+   *        the angle to rotate about the X axis in radians
+   * @return this
+   */
+  public M3 setAsXRotation(float angle) {
+    setXRot(angle);
+    return this;
+  }
+  
+  /**
+   * Sets the value of this matrix to a rotation matrix about the y axis by the
+   * passed angle.
+   * 
+   * @param angle
+   *        the angle to rotate about the Y axis in radians
+   * @return this
+   */
+  public M3 setAsYRotation(float angle) {
+    setYRot(angle);
+    return this;
+  }
+
+  /**
+   * Sets the value of this matrix to a rotation matrix about the z axis by the
+   * passed angle.
+   * 
+   * @param angle
+   *        the angle to rotate about the Z axis in radians
+   * @return this
+   */
+  public M3 setAsZRotation(float angle) {
+    setZRot(angle);
+    return this;
+  }
+
+  /**
+   * Multiplies each element of this matrix by a scalar.
+   * 
+   * @param scalar
+   *        The scalar multiplier.
+   */
+  public void scale(float scalar) {
+    mul33(scalar);
+  }
+
+  /**
+   * Sets the value of this matrix to the result of multiplying itself with
+   * matrix m1.
+   * 
+   * @param m1
+   *        the other matrix
+   */
+  public void mul(M3 m1) {
+    mul2(this, m1);
+  }
+
+  /**
+   * Sets the value of this matrix to the result of multiplying the two argument
+   * matrices together.
+   * 
+   * @param m1
+   *        the first matrix
+   * @param m2
+   *        the second matrix
+   */
+  public void mul2(M3 m1, M3 m2) {
+    // alias-safe way.
+    set9(m1.m00 * m2.m00 + m1.m01 * m2.m10 + m1.m02 * m2.m20, m1.m00 * m2.m01
+        + m1.m01 * m2.m11 + m1.m02 * m2.m21, m1.m00 * m2.m02 + m1.m01 * m2.m12
+        + m1.m02 * m2.m22,
+
+    m1.m10 * m2.m00 + m1.m11 * m2.m10 + m1.m12 * m2.m20, m1.m10 * m2.m01
+        + m1.m11 * m2.m11 + m1.m12 * m2.m21, m1.m10 * m2.m02 + m1.m11 * m2.m12
+        + m1.m12 * m2.m22,
+
+    m1.m20 * m2.m00 + m1.m21 * m2.m10 + m1.m22 * m2.m20, m1.m20 * m2.m01
+        + m1.m21 * m2.m11 + m1.m22 * m2.m21, m1.m20 * m2.m02 + m1.m21 * m2.m12
+        + m1.m22 * m2.m22);
+  }
+
+  /**
+   * Returns true if the Object o is of type Matrix3f and all of the data
+   * members of t1 are equal to the corresponding data members in this Matrix3f.
+   * 
+   * @param o
+   *        the object with which the comparison is made.
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof M3))
+      return false;
+    M3 m = (M3) o;
+    return m00 == m.m00 && m01 == m.m01 && m02 == m.m02 && m10 == m.m10
+        && m11 == m.m11 && m12 == m.m12 && m20 == m.m20 && m21 == m.m21
+        && m22 == m.m22;
+  }
+
+  /**
+   * Returns a hash number based on the data values in this object. Two
+   * different Matrix3f objects with identical data values (ie, returns true for
+   * equals(Matrix3f) ) will return the same hash number. Two objects with
+   * different data members may return the same hash value, although this is not
+   * likely.
+   * 
+   * @return the integer hash value
+   */
+  @Override
+  public int hashCode() {
+    return T3.floatToIntBits(m00) ^ T3.floatToIntBits(m01)
+        ^ T3.floatToIntBits(m02) ^ T3.floatToIntBits(m10)
+        ^ T3.floatToIntBits(m11) ^ T3.floatToIntBits(m12)
+        ^ T3.floatToIntBits(m20) ^ T3.floatToIntBits(m21)
+        ^ T3.floatToIntBits(m22);
+  }
+
+  /**
+   * Sets this matrix to all zeros.
+   */
+  public void setZero() {
+    clear33();
+  }
+
+  /**
+   * Sets 9 values
+   * 
+   * @param m00
+   * @param m01
+   * @param m02
+   * @param m10
+   * @param m11
+   * @param m12
+   * @param m20
+   * @param m21
+   * @param m22
+   */
+  private void set9(float m00, float m01, float m02, float m10, float m11,
+                   float m12, float m20, float m21, float m22) {
+    this.m00 = m00;
+    this.m01 = m01;
+    this.m02 = m02;
+    this.m10 = m10;
+    this.m11 = m11;
+    this.m12 = m12;
+    this.m20 = m20;
+    this.m21 = m21;
+    this.m22 = m22;
+  }
+
+  /**
+   * Returns a string that contains the values of this Matrix3f.
+   * 
+   * @return the String representation
+   */
+  @Override
+  public String toString() {
+    return "[\n  [" + m00 + "\t" + m01 + "\t" + m02 + "]" + "\n  [" + m10
+        + "\t" + m11 + "\t" + m12 + "]" + "\n  [" + m20 + "\t" + m21 + "\t"
+        + m22 + "] ]";
+  }
+
+  /**
+   * Sets the value of this matrix to the matrix conversion of the single
+   * precision axis and angle argument.
+   * 
+   * @param a
+   *        the axis and angle to be converted
+   * @return this
+   */
+  public M3 setAA(A4 a) {
+    setAA33(a);
+    return this;
+  }
+
+  /**
+   * 3D ball rotation from dx dy in-plane mouse motion
+   * adapted from Andrew Hanson
+   * Computer Graphics beyond the Third Dimension:
+   * Geometry, Orientation Control, and Rendering for
+   * Graphics in Dimensions Greater than Three
+   * Course Notes for SIGGRAPH â€™98
+   * http://www.cse.ohio-state.edu/~hwshen/888_su02/hanson_note.pdf
+   * 
+   * @param responseFactor Jmol uses 0.02 here
+   * @param dx
+   * @param dy
+   * @return true if successful; false if not;
+   */
+  public boolean setAsBallRotation(float responseFactor, float dx, float dy) {
+    float r = (float) Math.sqrt(dx * dx + dy * dy);
+    float th =  r * responseFactor;
+    if (th == 0) {
+      setScale(1);
+      return false;
+    }
+    float c = (float) Math.cos(th);
+    float s = (float) Math.sin(th);
+    float nx = -dy / r;
+    float ny = dx / r;
+    float c1 = c - 1;
+    m00 = 1 + c1 * nx * nx;
+    m01 = m10 = c1 * nx * ny;
+    m20 = -(m02 = s * nx);
+    m11 = 1 + c1 * ny * ny;
+    m21 = -(m12 = s * ny);
+    m22 = c;
+    return true;
+  }
+
+  public boolean isRotation() {
+    return (Math.abs(determinant3() - 1) < 0.001f);
+  }
+
+}
diff --git a/src/javajs/util/M34.java b/src/javajs/util/M34.java
new file mode 100644 (file)
index 0000000..260a82c
--- /dev/null
@@ -0,0 +1,416 @@
+package javajs.util;
+
+/**
+ * A base class for both M3 and M4 to conserve code size.
+ * 
+ * @author Kenji hiranabe
+ * 
+ *         additions by Bob Hanson hansonr@stolaf.edu 9/30/2012 for unique
+ *         constructor and method names for the optimization of compiled
+ *         JavaScript using Java2Script and for subclassing to M3 and M4
+ * 
+ */
+public abstract class M34 {
+
+  /**
+   * The first element of the first row
+   */
+  public float m00;
+
+  /**
+   * The second element of the first row.
+   */
+  public float m01;
+
+  /**
+   * third element of the first row.
+   */
+  public float m02;
+
+  /**
+   * The first element of the second row.
+   */
+  public float m10;
+
+  /**
+   * The second element of the second row.
+   */
+  public float m11;
+
+  /**
+   * The third element of the second row.
+   */
+  public float m12;
+
+  /**
+   * The first element of the third row.
+   */
+  public float m20;
+
+  /**
+   * The second element of the third row.
+   */
+  public float m21;
+
+  /**
+   * The third element of the third row.
+   */
+  public float m22;
+
+  protected void setAA33(A4 a) {
+    double x = a.x;
+    double y = a.y;
+    double z = a.z;
+    double angle = a.angle;
+    // Taken from Rick's which is taken from Wertz. pg. 412
+    // Bug Fixed and changed into right-handed by hiranabe
+    double n = Math.sqrt(x * x + y * y + z * z);
+    // zero-div may occur
+    n = 1 / n;
+    x *= n;
+    y *= n;
+    z *= n;
+    double c = Math.cos(angle);
+    double s = Math.sin(angle);
+    double omc = 1.0 - c;
+    m00 = (float) (c + x * x * omc);
+    m11 = (float) (c + y * y * omc);
+    m22 = (float) (c + z * z * omc);
+
+    double tmp1 = x * y * omc;
+    double tmp2 = z * s;
+    m01 = (float) (tmp1 - tmp2);
+    m10 = (float) (tmp1 + tmp2);
+
+    tmp1 = x * z * omc;
+    tmp2 = y * s;
+    m02 = (float) (tmp1 + tmp2);
+    m20 = (float) (tmp1 - tmp2);
+
+    tmp1 = y * z * omc;
+    tmp2 = x * s;
+    m12 = (float) (tmp1 - tmp2);
+    m21 = (float) (tmp1 + tmp2);
+  }
+
+  public void rotate(T3 t) {
+    // alias-safe
+    rotate2(t, t);
+  }
+
+  /**
+   * Transform the vector vec using this Matrix3f and place the result into
+   * vecOut.
+   * 
+   * @param t
+   *        the single precision vector to be transformed
+   * @param result
+   *        the vector into which the transformed values are placed
+   */
+  public void rotate2(T3 t, T3 result) {
+    // alias-safe
+    result.set(m00 * t.x + m01 * t.y + m02 * t.z, m10 * t.x + m11 * t.y + m12
+        * t.z, m20 * t.x + m21 * t.y + m22 * t.z);
+  }
+
+
+  /**
+   * Sets the value of this matrix to the double value of the Matrix3f argument.
+   * 
+   * @param m1
+   *        the matrix3f
+   */
+  protected void setM33(M34 m1) {
+    m00 = m1.m00;
+    m01 = m1.m01;
+    m02 = m1.m02;
+    m10 = m1.m10;
+    m11 = m1.m11;
+    m12 = m1.m12;
+    m20 = m1.m20;
+    m21 = m1.m21;
+    m22 = m1.m22;
+  }
+
+  protected void clear33() {
+    m00 = m01 = m02 = m10 = m11 = m12 = m20 = m21 = m22 = 0.0f;
+  }
+
+  protected void set33(int row, int col, float v) {
+    switch (row) {
+    case 0:
+      switch (col) {
+      case 0:
+        m00 = v;
+        return;
+      case 1:
+        m01 = v;
+        return;
+      case 2: 
+        m02 = v;
+        return;
+      }
+      break;
+    case 1:
+      switch (col) {
+      case 0:
+        m10 = v;
+        return;
+      case 1:
+        m11 = v;
+        return;
+      case 2: 
+        m12 = v;
+        return;
+      }
+      break;
+    case 2: 
+      switch (col) {
+      case 0:
+        m20 = v;
+        return;
+      case 1:
+        m21 = v;
+        return;
+      case 2: 
+        m22 = v;
+        return;
+      }
+      break;
+    }
+    err();
+  }
+
+  protected float get33(int row, int col) {
+    switch (row) {
+    case 0:
+      switch (col) {
+      case 0:
+        return m00;
+      case 1:
+        return m01;
+      case 2:
+        return m02;
+      }
+      break;
+    case 1:
+      switch (col) {
+      case 0:
+        return m10;
+      case 1:
+        return m11;
+      case 2:
+        return m12;
+      }
+      break;
+    case 2:
+      switch (col) {
+      case 0:
+        return m20;
+      case 1:
+        return m21;
+      case 2:
+        return m22;
+      }
+      break;
+    }
+    err();
+    return 0;
+  }
+
+  protected void setRow33(int row, float v[]) {
+    switch (row) {
+    case 0:
+      m00 = v[0];
+      m01 = v[1];
+      m02 = v[2];
+      return;
+    case 1:
+      m10 = v[0];
+      m11 = v[1];
+      m12 = v[2];
+      return;
+    case 2:
+      m20 = v[0];
+      m21 = v[1];
+      m22 = v[2];
+      return;
+    default:
+      err();
+    }
+  }
+  
+  public abstract void getRow(int row, float v[]);
+
+  protected void getRow33(int row, float v[]) {
+    switch (row) {
+    case 0:
+      v[0] = m00;
+      v[1] = m01;
+      v[2] = m02;
+      return;
+    case 1:
+      v[0] = m10;
+      v[1] = m11;
+      v[2] = m12;
+      return;
+    case 2:
+      v[0] = m20;
+      v[1] = m21;
+      v[2] = m22;
+      return;
+    }
+    err();
+  }
+
+  protected void setColumn33(int column, float v[]) {
+    switch(column) {
+    case 0:
+      m00 = v[0];
+      m10 = v[1];
+      m20 = v[2];
+      break;
+    case 1:
+      m01 = v[0];
+      m11 = v[1];
+      m21 = v[2];
+      break;
+    case 2:
+      m02 = v[0];
+      m12 = v[1];
+      m22 = v[2];
+      break;
+     default:
+      err();
+    }
+  }
+
+  protected void getColumn33(int column, float v[]) {
+    switch(column) {
+    case 0:
+      v[0] = m00;
+      v[1] = m10;
+      v[2] = m20;
+      break;
+    case 1:
+      v[0] = m01;
+      v[1] = m11;
+      v[2] = m21;
+      break;
+    case 2:
+      v[0] = m02;
+      v[1] = m12;
+      v[2] = m22;
+      break;
+    default:
+      err();
+    }
+  }
+
+  protected void add33(M34 m1) {
+    m00 += m1.m00;
+    m01 += m1.m01;
+    m02 += m1.m02;
+    m10 += m1.m10;
+    m11 += m1.m11;
+    m12 += m1.m12;
+    m20 += m1.m20;
+    m21 += m1.m21;
+    m22 += m1.m22;
+  }
+
+  protected void sub33(M34 m1) {
+    m00 -= m1.m00;
+    m01 -= m1.m01;
+    m02 -= m1.m02;
+    m10 -= m1.m10;
+    m11 -= m1.m11;
+    m12 -= m1.m12;
+    m20 -= m1.m20;
+    m21 -= m1.m21;
+    m22 -= m1.m22;
+  }
+
+  protected void mul33(float x) {
+    m00 *= x;
+    m01 *= x;
+    m02 *= x;
+    m10 *= x;
+    m11 *= x;
+    m12 *= x;
+    m20 *= x;
+    m21 *= x;
+    m22 *= x;
+  }
+
+  protected void transpose33() {
+    float tmp = m01;
+    m01 = m10;
+    m10 = tmp;
+
+    tmp = m02;
+    m02 = m20;
+    m20 = tmp;
+
+    tmp = m12;
+    m12 = m21;
+    m21 = tmp;
+  }
+
+  protected void setXRot(float angle) {
+    double c = Math.cos(angle);
+    double s = Math.sin(angle);
+    m00 = 1.0f;
+    m01 = 0.0f;
+    m02 = 0.0f;
+    m10 = 0.0f;
+    m11 = (float) c;
+    m12 = (float) -s;
+    m20 = 0.0f;
+    m21 = (float) s;
+    m22 = (float) c;
+  }
+
+  protected void setYRot(float angle) {
+    double c = Math.cos(angle);
+    double s = Math.sin(angle);
+    m00 = (float) c;
+    m01 = 0.0f;
+    m02 = (float) s;
+    m10 = 0.0f;
+    m11 = 1.0f;
+    m12 = 0.0f;
+    m20 = (float) -s;
+    m21 = 0.0f;
+    m22 = (float) c;
+  }
+  
+  protected void setZRot(float angle) {
+    double c = Math.cos(angle);
+    double s = Math.sin(angle);
+    m00 = (float) c;
+    m01 = (float) -s;
+    m02 = 0.0f;
+    m10 = (float) s;
+    m11 = (float) c;
+    m12 = 0.0f;
+    m20 = 0.0f;
+    m21 = 0.0f;
+    m22 = 1.0f;
+  }
+  
+  /**
+   * @return 3x3 determinant
+   */
+  public float determinant3() {
+    return m00 * (m11 * m22 - m21 * m12) - m01 * (m10 * m22 - m20 * m12) + m02
+        * (m10 * m21 - m20 * m11);
+  }
+  
+  protected void err() {
+    throw new ArrayIndexOutOfBoundsException(
+        "matrix column/row out of bounds");
+  }
+
+
+}
diff --git a/src/javajs/util/M4.java b/src/javajs/util/M4.java
new file mode 100644 (file)
index 0000000..58bd355
--- /dev/null
@@ -0,0 +1,942 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+/**
+ * A single precision floating point 4 by 4 matrix.
+ * 
+ * @author Kenji hiranabe
+ * 
+ *         additions by Bob Hanson hansonr@stolaf.edu 9/30/2012 for unique
+ *         constructor and method names for the optimization of compiled
+ *         JavaScript using Java2Script
+ */
+public class M4 extends M34 {
+
+  /**
+   * The fourth element of the first row.
+   */
+  public float m03;
+
+  /**
+   * The fourth element of the second row.
+   */
+  public float m13;
+
+  /**
+   * The fourth element of the third row.
+   */
+  public float m23;
+
+  /**
+   * The first element of the fourth row.
+   */
+  public float m30;
+
+  /**
+   * The second element of the fourth row.
+   */
+  public float m31;
+
+  /**
+   * The third element of the fourth row.
+   */
+  public float m32;
+
+  /**
+   * The fourth element of the fourth row.
+   */
+  public float m33 = 0;
+
+  /**
+   * all zeros
+   */
+  public M4() {
+  }
+  /**
+   * Constructs and initializes a Matrix4f from the specified 16 element array.
+   * this.m00 =v[0], this.m01=v[1], etc.
+   * 
+   * @param v
+   *        the array of length 16 containing in order
+   * @return m
+   */
+  public static M4 newA16(float[] v) {
+    M4 m = new M4();
+    m.m00 = v[0];
+    m.m01 = v[1];
+    m.m02 = v[2];
+    m.m03 = v[3];
+
+    m.m10 = v[4];
+    m.m11 = v[5];
+    m.m12 = v[6];
+    m.m13 = v[7];
+
+    m.m20 = v[8];
+    m.m21 = v[9];
+    m.m22 = v[10];
+    m.m23 = v[11];
+
+    m.m30 = v[12];
+    m.m31 = v[13];
+    m.m32 = v[14];
+    m.m33 = v[15];
+
+    return m;
+  }
+
+  /**
+   * Constructs a new matrix with the same values as the Matrix4f parameter.
+   * 
+   * @param m1
+   *        the source matrix
+   * @return m
+   */
+  public static M4 newM4(M4 m1) {
+    M4 m = new M4();
+    if (m1 == null) {
+      m.setIdentity();
+      return m;
+    }
+    m.setToM3(m1);
+    m.m03 = m1.m03;
+    m.m13 = m1.m13;
+    m.m23 = m1.m23;
+    m.m30 = m1.m30;
+    m.m31 = m1.m31;
+    m.m32 = m1.m32;
+    m.m33 = m1.m33;
+    return m;
+  }
+
+  /**
+   * Constructs and initializes a Matrix4f from the rotation matrix and
+   * translation.
+   * 
+   * @param m1
+   *        The rotation matrix representing the rotational components
+   * @param t
+   *        The translational components of the matrix
+   * @return m
+   */
+  public static M4 newMV(M3 m1, T3 t) {
+    M4 m = new M4();
+    m.setMV(m1, t);
+    return m;
+  }
+
+  /**
+   * Sets this matrix to all zeros.
+   */
+  public void setZero() {
+    clear33();
+    m03 = m13 = m23 = m30 = m31 = m32 = m33 = 0.0f;
+  }
+
+  /**
+   * Sets this Matrix4f to identity.
+   */
+  public void setIdentity() {
+    setZero();
+    m00 = m11 = m22 = m33 = 1.0f;
+  }
+
+  /**
+   * Sets the value of this matrix to a copy of the passed matrix m1.
+   * 
+   * @param m1
+   *        the matrix to be copied
+   * @return this
+   */
+  public M4 setM4(M4 m1) {
+    setM33(m1);
+    m03 = m1.m03;
+    m13 = m1.m13;
+    m23 = m1.m23;
+    m30 = m1.m30;
+    m31 = m1.m31;
+    m32 = m1.m32;
+    m33 = m1.m33;
+    return this;
+  }
+
+  /**
+   * Initializes a Matrix4f from the rotation matrix and translation.
+   * 
+   * @param m1
+   *        The rotation matrix representing the rotational components
+   * @param t
+   *        The translational components of the matrix
+   */
+  public void setMV(M3 m1, T3 t) {
+    setM33(m1);
+    setTranslation(t);
+    m33 = 1;
+  }
+
+  /**
+   * Sets the rotational component (upper 3x3) of this matrix to the matrix
+   * values in the single precision Matrix3f argument; the other elements of
+   * this matrix are initialized as if this were an identity matrix (ie, affine
+   * matrix with no translational component).
+   * 
+   * @param m1
+   *        the 3x3 matrix
+   */
+  public void setToM3(M34 m1) {
+    setM33(m1);
+    m03 = m13 = m23 = m30 = m31 = m32 = 0.0f;
+    m33 = 1.0f;
+  }
+
+  /**
+   * Sets the rotational component (upper 3x3) of this matrix 
+   * to a rotation given by an axis angle
+   * 
+   * @param a
+   *        the axis and angle to be converted
+   */
+  public void setToAA(A4 a) {
+    setIdentity();
+    setAA33(a);
+  }
+
+  /**
+   * Sets the values in this Matrix4f equal to the row-major array parameter
+   * (ie, the first four elements of the array will be copied into the first row
+   * of this matrix, etc.).
+   * 
+   * @param m
+   */
+  public void setA(float m[]) {
+    m00 = m[0];
+    m01 = m[1];
+    m02 = m[2];
+    m03 = m[3];
+    m10 = m[4];
+    m11 = m[5];
+    m12 = m[6];
+    m13 = m[7];
+    m20 = m[8];
+    m21 = m[9];
+    m22 = m[10];
+    m23 = m[11];
+    m30 = m[12];
+    m31 = m[13];
+    m32 = m[14];
+    m33 = m[15];
+  }
+
+  /**
+   * Modifies the translational components of this matrix to the values of the
+   * Vector3f argument; the other values of this matrix are not modified.
+   * 
+   * @param trans
+   *        the translational component
+   */
+  public void setTranslation(T3 trans) {
+    m03 = trans.x;
+    m13 = trans.y;
+    m23 = trans.z;
+  }
+
+  /**
+   * Sets the specified element of this matrix4f to the value provided.
+   * 
+   * @param row
+   *        the row number to be modified (zero indexed)
+   * @param col
+   *        the column number to be modified (zero indexed)
+   * @param v
+   *        the new value
+   */
+  public void setElement(int row, int col, float v) {
+    if (row < 3 && col < 3) {
+      set33(row, col, v);
+      return;
+    }
+    if (row > 3 || col > 3)
+      err();
+    switch (row) {
+    case 0:
+      m03 = v;
+      return;
+    case 1:
+      m13 = v;
+      return;
+    case 2:
+      m23 = v;
+      return;
+    }
+    switch (col) {
+    case 0:
+      m30 = v;
+      return;
+    case 1:
+      m31 = v;
+      return;
+    case 2:
+      m32 = v;
+      return;
+    case 3:
+      m33 = v;
+      return;
+    }
+  }
+
+  /**
+   * Retrieves the value at the specified row and column of this matrix.
+   * 
+   * @param row
+   *        the row number to be retrieved (zero indexed)
+   * @param col
+   *        the column number to be retrieved (zero indexed)
+   * @return the value at the indexed element
+   */
+  public float getElement(int row, int col) {
+    if (row < 3 && col < 3)
+      return get33(row, col);
+    if (row > 3 || col > 3) {
+      err();
+      return 0;
+    }
+    switch (row) {
+    case 0:
+      return m03;
+    case 1:
+      return m13;
+    case 2:
+      return m23;
+    default:
+      switch (col) {
+      case 0:
+        return m30;
+      case 1:
+        return m31;
+      case 2:
+        return m32;
+      default:
+        return m33;
+      }
+    }
+  }
+
+  /**
+   * Retrieves the translational components of this matrix.
+   * 
+   * @param trans
+   *        the vector that will receive the translational component
+   */
+  public void getTranslation(T3 trans) {
+    trans.x = m03;
+    trans.y = m13;
+    trans.z = m23;
+  }
+
+  /**
+   * Gets the upper 3x3 values of this matrix and places them into the matrix
+   * m1.
+   * 
+   * @param m1
+   *        The matrix that will hold the values
+   */
+  public void getRotationScale(M3 m1) {
+    m1.m00 = m00;
+    m1.m01 = m01;
+    m1.m02 = m02;
+    m1.m10 = m10;
+    m1.m11 = m11;
+    m1.m12 = m12;
+    m1.m20 = m20;
+    m1.m21 = m21;
+    m1.m22 = m22;
+  }
+
+  /**
+   * Replaces the upper 3x3 matrix values of this matrix with the values in the
+   * matrix m1.
+   * 
+   * @param m1
+   *        The matrix that will be the new upper 3x3
+   */
+  public void setRotationScale(M3 m1) {
+    m00 = m1.m00;
+    m01 = m1.m01;
+    m02 = m1.m02;
+    m10 = m1.m10;
+    m11 = m1.m11;
+    m12 = m1.m12;
+    m20 = m1.m20;
+    m21 = m1.m21;
+    m22 = m1.m22;
+  }
+
+  /**
+   * Sets the specified row of this matrix4f to the four values provided.
+   * 
+   * @param row
+   *        the row number to be modified (zero indexed)
+   * @param v
+   *        the replacement row
+   */
+  public void setRowA(int row, float v[]) {
+    if (row < 3)
+      setRow33(row, v);
+    switch (row) {
+    case 0:
+      m03 = v[3];
+      return;
+    case 1:
+      m13 = v[3];
+      return;
+    case 2:
+      m23 = v[3];
+      return;
+    case 3:
+      m30 = v[0];
+      m31 = v[1];
+      m32 = v[2];
+      m33 = v[3];
+      return;
+    }
+    err();
+  }
+
+  /**
+   * Copies the matrix values in the specified row into the array parameter.
+   * 
+   * @param row
+   *        the matrix row
+   * @param v
+   *        The array into which the matrix row values will be copied
+   */
+  @Override
+  public void getRow(int row, float v[]) {
+    if (row < 3)
+      getRow33(row, v);
+    switch (row) {
+    case 0:
+      v[3] = m03;
+      return;
+    case 1:
+      v[3] = m13;
+      return;
+    case 2:
+      v[3] = m23;
+      return;
+    case 3:
+      v[0] = m30;
+      v[1] = m31;
+      v[2] = m32;
+      v[3] = m33;
+      return;
+    }
+    err();
+  }
+
+  /**
+   * Sets the specified column of this matrix4f to the four values provided.
+   * 
+   * @param column
+   *        the column number to be modified (zero indexed)
+   * @param x
+   *        the first row element
+   * @param y
+   *        the second row element
+   * @param z
+   *        the third row element
+   * @param w
+   *        the fourth row element
+   */
+  public void setColumn4(int column, float x, float y, float z, float w) {
+    if (column == 0) {
+      m00 = x;
+      m10 = y;
+      m20 = z;
+      m30 = w;
+    } else if (column == 1) {
+      m01 = x;
+      m11 = y;
+      m21 = z;
+      m31 = w;
+    } else if (column == 2) {
+      m02 = x;
+      m12 = y;
+      m22 = z;
+      m32 = w;
+    } else if (column == 3) {
+      m03 = x;
+      m13 = y;
+      m23 = z;
+      m33 = w;
+    } else {
+      err();
+    }
+  }
+
+  /**
+   * Sets the specified column of this matrix4f to the four values provided.
+   * 
+   * @param column
+   *        the column number to be modified (zero indexed)
+   * @param v
+   *        the replacement column
+   */
+  public void setColumnA(int column, float v[]) {
+    if (column < 3)
+      setColumn33(column, v);
+    switch (column) {
+    case 0:
+      m30 = v[3];
+      return;
+    case 1:
+      m31 = v[3];
+      return;
+    case 2:
+      m32 = v[3];
+      return;
+    case 3:
+      m03 = v[0];
+      m13 = v[1];
+      m23 = v[2];
+      m33 = v[3];
+      return;
+    default:
+      err();
+    }
+  }
+
+  /**
+   * Copies the matrix values in the specified column into the array parameter.
+   * 
+   * @param column
+   *        the matrix column
+   * @param v
+   *        The array into which the matrix column values will be copied
+   */
+  public void getColumn(int column, float v[]) {
+    if (column < 3)
+      getColumn33(column, v);
+    switch (column) {
+    case 0:
+      v[3] = m30;
+      return;
+    case 1:
+      v[3] = m31;
+      return;
+    case 2:
+      v[3] = m32;
+      return;
+    case 3:
+      v[0] = m03;
+      v[1] = m13;
+      v[2] = m23;
+      v[3] = m33;
+      return;
+    default:
+      err();
+    }
+  }
+
+  /**
+   * Sets the value of this matrix to the matrix difference of itself and matrix
+   * m1 (this = this - m1).
+   * 
+   * @param m1
+   *        the other matrix
+   */
+  public void sub(M4 m1) {
+    sub33(m1);
+    m03 -= m1.m03;
+    m13 -= m1.m13;
+    m23 -= m1.m23;
+    m30 -= m1.m30;
+    m31 -= m1.m31;
+    m32 -= m1.m32;
+    m33 -= m1.m33;
+  }
+
+  /**
+   * Sets the value of this matrix to its transpose.
+   */
+  public void transpose() {
+    transpose33();
+    float tmp = m03;
+    m03 = m30;
+    m30 = tmp;
+
+    tmp = m13;
+    m13 = m31;
+    m31 = tmp;
+
+    tmp = m23;
+    m23 = m32;
+    m32 = tmp;
+  }
+
+  /**
+   * Sets the value of this matrix to its inverse.
+   * @return this
+   */
+  public M4 invert() {
+    float s = determinant4();
+    if (s == 0.0)
+      return this;
+    s = 1 / s;
+    // alias-safe way.
+    // less *,+,- calculation than expanded expression.
+    set(m11 * (m22 * m33 - m23 * m32) + m12 * (m23 * m31 - m21 * m33) + m13
+        * (m21 * m32 - m22 * m31), m21 * (m02 * m33 - m03 * m32) + m22
+        * (m03 * m31 - m01 * m33) + m23 * (m01 * m32 - m02 * m31), m31
+        * (m02 * m13 - m03 * m12) + m32 * (m03 * m11 - m01 * m13) + m33
+        * (m01 * m12 - m02 * m11), m01 * (m13 * m22 - m12 * m23) + m02
+        * (m11 * m23 - m13 * m21) + m03 * (m12 * m21 - m11 * m22),
+
+    m12 * (m20 * m33 - m23 * m30) + m13 * (m22 * m30 - m20 * m32) + m10
+        * (m23 * m32 - m22 * m33), m22 * (m00 * m33 - m03 * m30) + m23
+        * (m02 * m30 - m00 * m32) + m20 * (m03 * m32 - m02 * m33), m32
+        * (m00 * m13 - m03 * m10) + m33 * (m02 * m10 - m00 * m12) + m30
+        * (m03 * m12 - m02 * m13), m02 * (m13 * m20 - m10 * m23) + m03
+        * (m10 * m22 - m12 * m20) + m00 * (m12 * m23 - m13 * m22),
+
+    m13 * (m20 * m31 - m21 * m30) + m10 * (m21 * m33 - m23 * m31) + m11
+        * (m23 * m30 - m20 * m33), m23 * (m00 * m31 - m01 * m30) + m20
+        * (m01 * m33 - m03 * m31) + m21 * (m03 * m30 - m00 * m33), m33
+        * (m00 * m11 - m01 * m10) + m30 * (m01 * m13 - m03 * m11) + m31
+        * (m03 * m10 - m00 * m13), m03 * (m11 * m20 - m10 * m21) + m00
+        * (m13 * m21 - m11 * m23) + m01 * (m10 * m23 - m13 * m20),
+
+    m10 * (m22 * m31 - m21 * m32) + m11 * (m20 * m32 - m22 * m30) + m12
+        * (m21 * m30 - m20 * m31), m20 * (m02 * m31 - m01 * m32) + m21
+        * (m00 * m32 - m02 * m30) + m22 * (m01 * m30 - m00 * m31), m30
+        * (m02 * m11 - m01 * m12) + m31 * (m00 * m12 - m02 * m10) + m32
+        * (m01 * m10 - m00 * m11), m00 * (m11 * m22 - m12 * m21) + m01
+        * (m12 * m20 - m10 * m22) + m02 * (m10 * m21 - m11 * m20));
+    scale(s);
+    return this;
+  }
+
+  /**
+   * Sets 16 values
+   * 
+   * @param m00
+   * @param m01
+   * @param m02
+   * @param m03
+   * @param m10
+   * @param m11
+   * @param m12
+   * @param m13
+   * @param m20
+   * @param m21
+   * @param m22
+   * @param m23
+   * @param m30
+   * @param m31
+   * @param m32
+   * @param m33
+   */
+  private void set(float m00, float m01, float m02, float m03, float m10,
+                   float m11, float m12, float m13, float m20, float m21,
+                   float m22, float m23, float m30, float m31, float m32,
+                   float m33) {
+    this.m00 = m00;
+    this.m01 = m01;
+    this.m02 = m02;
+    this.m03 = m03;
+    this.m10 = m10;
+    this.m11 = m11;
+    this.m12 = m12;
+    this.m13 = m13;
+    this.m20 = m20;
+    this.m21 = m21;
+    this.m22 = m22;
+    this.m23 = m23;
+    this.m30 = m30;
+    this.m31 = m31;
+    this.m32 = m32;
+    this.m33 = m33;
+  }
+  /**
+   * Computes the determinant of this matrix.
+   * 
+   * @return the determinant of the matrix
+   */
+  public float determinant4() {
+    // less *,+,- calculation than expanded expression.
+    return (m00 * m11 - m01 * m10) * (m22 * m33 - m23 * m32)
+        - (m00 * m12 - m02 * m10) * (m21 * m33 - m23 * m31)
+        + (m00 * m13 - m03 * m10) * (m21 * m32 - m22 * m31)
+        + (m01 * m12 - m02 * m11) * (m20 * m33 - m23 * m30)
+        - (m01 * m13 - m03 * m11) * (m20 * m32 - m22 * m30)
+        + (m02 * m13 - m03 * m12) * (m20 * m31 - m21 * m30);
+
+  }
+
+  /**
+   * Multiplies each element of this matrix by a scalar.
+   * 
+   * @param scalar
+   *        The scalar multiplier.
+   */
+  public void scale(float scalar) {
+    mul33(scalar);
+    m03 *= scalar;
+    m13 *= scalar;
+    m23 *= scalar;
+    m30 *= scalar;
+    m31 *= scalar;
+    m32 *= scalar;
+    m33 *= scalar;
+  }
+
+  /**
+   * Sets the value of this matrix to the result of multiplying itself with
+   * matrix m1.
+   * 
+   * @param m1
+   *        the other matrix
+   */
+  public void mul(M4 m1) {
+    mul2(this, m1);
+  }
+
+  /**
+   * Sets the value of this matrix to the result of multiplying the two argument
+   * matrices together.
+   * 
+   * @param m1
+   *        the first matrix
+   * @param m2
+   *        the second matrix
+   */
+  public void mul2(M4 m1, M4 m2) {
+    // alias-safe way.
+    set(m1.m00 * m2.m00 + m1.m01 * m2.m10 + m1.m02 * m2.m20 + m1.m03 * m2.m30,
+        m1.m00 * m2.m01 + m1.m01 * m2.m11 + m1.m02 * m2.m21 + m1.m03 * m2.m31,
+        m1.m00 * m2.m02 + m1.m01 * m2.m12 + m1.m02 * m2.m22 + m1.m03 * m2.m32,
+        m1.m00 * m2.m03 + m1.m01 * m2.m13 + m1.m02 * m2.m23 + m1.m03 * m2.m33,
+
+        m1.m10 * m2.m00 + m1.m11 * m2.m10 + m1.m12 * m2.m20 + m1.m13 * m2.m30,
+        m1.m10 * m2.m01 + m1.m11 * m2.m11 + m1.m12 * m2.m21 + m1.m13 * m2.m31,
+        m1.m10 * m2.m02 + m1.m11 * m2.m12 + m1.m12 * m2.m22 + m1.m13 * m2.m32,
+        m1.m10 * m2.m03 + m1.m11 * m2.m13 + m1.m12 * m2.m23 + m1.m13 * m2.m33,
+
+        m1.m20 * m2.m00 + m1.m21 * m2.m10 + m1.m22 * m2.m20 + m1.m23 * m2.m30,
+        m1.m20 * m2.m01 + m1.m21 * m2.m11 + m1.m22 * m2.m21 + m1.m23 * m2.m31,
+        m1.m20 * m2.m02 + m1.m21 * m2.m12 + m1.m22 * m2.m22 + m1.m23 * m2.m32,
+        m1.m20 * m2.m03 + m1.m21 * m2.m13 + m1.m22 * m2.m23 + m1.m23 * m2.m33,
+
+        m1.m30 * m2.m00 + m1.m31 * m2.m10 + m1.m32 * m2.m20 + m1.m33 * m2.m30,
+        m1.m30 * m2.m01 + m1.m31 * m2.m11 + m1.m32 * m2.m21 + m1.m33 * m2.m31,
+        m1.m30 * m2.m02 + m1.m31 * m2.m12 + m1.m32 * m2.m22 + m1.m33 * m2.m32,
+        m1.m30 * m2.m03 + m1.m31 * m2.m13 + m1.m32 * m2.m23 + m1.m33 * m2.m33);
+  }
+
+  /**
+   * Transform the vector vec using this Matrix4f and place the result back into
+   * vec.
+   * 
+   * @param vec
+   *        the single precision vector to be transformed
+   */
+  public void transform(T4 vec) {
+    transform2(vec, vec);
+  }
+
+  /**
+   * Transform the vector vec using this Matrix4f and place the result into
+   * vecOut.
+   * 
+   * @param vec
+   *        the single precision vector to be transformed
+   * @param vecOut
+   *        the vector into which the transformed values are placed
+   */
+  public void transform2(T4 vec, T4 vecOut) {
+    // alias-safe
+    vecOut.set4(m00 * vec.x + m01 * vec.y + m02 * vec.z + m03 * vec.w, m10
+        * vec.x + m11 * vec.y + m12 * vec.z + m13 * vec.w, m20 * vec.x + m21
+        * vec.y + m22 * vec.z + m23 * vec.w, m30 * vec.x + m31 * vec.y + m32
+        * vec.z + m33 * vec.w);
+  }
+
+  /**
+   * Transforms the point parameter with this Matrix4f and places the result
+   * back into point. The fourth element of the point input parameter is assumed
+   * to be one.
+   * 
+   * @param point
+   *        the input point to be transformed.
+   */
+  public void rotTrans(T3 point) {
+    rotTrans2(point, point);
+  }
+
+  /**
+   * Transforms the point parameter with this Matrix4f and places the result
+   * into pointOut. The fourth element of the point input parameter is assumed to
+   * be one. point may be pointOut
+   * 
+   * @param point
+   *        the input point to be transformed.
+   * @param pointOut
+   *        the transformed point
+   * @return pointOut
+   */
+  public T3 rotTrans2(T3 point, T3 pointOut) {
+      pointOut.set(
+          m00 * point.x + m01 * point.y + m02 * point.z + m03, 
+          m10 * point.x + m11 * point.y + m12 * point.z + m13, 
+          m20 * point.x + m21 * point.y + m22 * point.z + m23);
+      return pointOut;
+  }
+
+  /**
+   * Sets the value of this matrix to a rotation matrix about the w axis by the
+   * passed angle.
+   * 
+   * @param angle
+   *        the angle to rotate about the W axis in radians
+   * @return this
+   */
+  public M4 setAsXYRotation(float angle) {
+    setIdentity();
+    double c = Math.cos(angle);
+    double s = Math.sin(angle);
+    m22 = (float) c;
+    m23 = (float) -s;
+    m32 = (float) s;
+    m33 = (float) c;
+    return this;
+  }
+
+  /**
+   * Sets the value of this matrix to a rotation matrix about the w axis by the
+   * passed angle.
+   * 
+   * @param angle
+   *        the angle to rotate about the W axis in radians
+   * @return this
+   */
+  public M4 setAsYZRotation(float angle) {
+    setIdentity();
+    double c = Math.cos(angle);
+    double s = Math.sin(angle);
+    m00 = (float) c;
+    m03 = (float) -s;
+    m30 = (float) s;
+    m33 = (float) c;
+    return this;
+  }
+
+  /**
+   * Sets the value of this matrix to a rotation matrix about the w axis by the
+   * passed angle.
+   * 
+   * @param angle
+   *        the angle to rotate about the W axis in radians
+   * @return this
+   */
+  public M4 setAsXZRotation(float angle) {
+    setIdentity();
+    double c = Math.cos(angle);
+    double s = Math.sin(angle);
+    m11 = (float) c;
+    m13 = (float) -s;
+    m31 = (float) s;
+    m33 = (float) c;
+    return this;
+  }
+
+  /**
+   * Returns true if the Object o is of type Matrix4f and all of the data
+   * members of t1 are equal to the corresponding data members in this Matrix4f.
+   * 
+   * @param o
+   *        the object with which the comparison is made.
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof M4))
+      return false;
+    M4 m = (M4) o;
+    return (this.m00 == m.m00 && this.m01 == m.m01 && this.m02 == m.m02
+        && this.m03 == m.m03 && this.m10 == m.m10 && this.m11 == m.m11
+        && this.m12 == m.m12 && this.m13 == m.m13 && this.m20 == m.m20
+        && this.m21 == m.m21 && this.m22 == m.m22 && this.m23 == m.m23
+        && this.m30 == m.m30 && this.m31 == m.m31 && this.m32 == m.m32 && this.m33 == m.m33);
+  }
+
+  /**
+   * Returns a hash number based on the data values in this object. Two
+   * different Matrix4f objects with identical data values (ie, returns true for
+   * equals(Matrix4f) ) will return the same hash number. Two objects with
+   * different data members may return the same hash value, although this is not
+   * likely.
+   * 
+   * @return the integer hash value
+   */
+  @Override
+  public int hashCode() {
+    return T3.floatToIntBits(m00) ^ T3.floatToIntBits(m01)
+        ^ T3.floatToIntBits(m02) ^ T3.floatToIntBits(m03)
+        ^ T3.floatToIntBits(m10) ^ T3.floatToIntBits(m11)
+        ^ T3.floatToIntBits(m12) ^ T3.floatToIntBits(m13)
+        ^ T3.floatToIntBits(m20) ^ T3.floatToIntBits(m21)
+        ^ T3.floatToIntBits(m22) ^ T3.floatToIntBits(m23)
+        ^ T3.floatToIntBits(m30) ^ T3.floatToIntBits(m31)
+        ^ T3.floatToIntBits(m32) ^ T3.floatToIntBits(m33);
+  }
+
+  /**
+   * Returns a string that contains the values of this Matrix4f.
+   * 
+   * @return the String representation
+   */
+  @Override
+  public String toString() {
+    return "[\n  [" + m00 + "\t" + m01 + "\t" + m02 + "\t" + m03 + "]"
+        + "\n  [" + m10 + "\t" + m11 + "\t" + m12 + "\t" + m13 + "]" + "\n  ["
+        + m20 + "\t" + m21 + "\t" + m22 + "\t" + m23 + "]" + "\n  [" + m30
+        + "\t" + m31 + "\t" + m32 + "\t" + m33 + "] ]";
+  }
+  public M4 round(float f) {
+    m00 = rnd(m00, f);
+    m01 = rnd(m01, f);
+    m02 = rnd(m02, f);
+    m03 = rnd(m03, f);
+    m10 = rnd(m10, f);
+    m11 = rnd(m11, f);
+    m12 = rnd(m12, f);
+    m13 = rnd(m13, f);
+    m20 = rnd(m20, f);
+    m21 = rnd(m21, f);
+    m22 = rnd(m22, f);
+    m23 = rnd(m23, f);
+    m30 = rnd(m30, f);
+    m31 = rnd(m31, f);
+    m32 = rnd(m32, f);
+    m33 = rnd(m33, f);
+    return this;
+  }
+
+  private float rnd(float n, float f) {
+    return (Math.abs(n) < f ? 0 : n);
+  }
+}
diff --git a/src/javajs/util/Matrix.java b/src/javajs/util/Matrix.java
new file mode 100644 (file)
index 0000000..cea5a04
--- /dev/null
@@ -0,0 +1,457 @@
+package javajs.util;
+
+/**
+ * 
+ * streamlined and refined for Jmol by Bob Hanson
+ * 
+ * from http://math.nist.gov/javanumerics/jama/
+ * 
+ * Jama = Java Matrix class.
+ * 
+ * @author The MathWorks, Inc. and the National Institute of Standards and
+ *         Technology.
+ * @version 5 August 1998
+ */
+
+public class Matrix implements Cloneable {
+
+  public double[][] a;
+  protected int m, n;
+
+  /**
+   * Construct a matrix quickly without checking arguments.
+   * 
+   * @param a
+   *        Two-dimensional array of doubles or null
+   * @param m
+   *        Number of rows.
+   * @param n
+   *        Number of colums.
+   */
+
+  public Matrix(double[][] a, int m, int n) {
+    this.a = (a == null ? new double[m][n] : a);
+    this.m = m;
+    this.n = n;
+  }
+
+  /**
+   * Get row dimension.
+   * 
+   * @return m, the number of rows.
+   */
+
+  public int getRowDimension() {
+    return m;
+  }
+
+  /**
+   * Get column dimension.
+   * 
+   * @return n, the number of columns.
+   */
+
+  public int getColumnDimension() {
+    return n;
+  }
+
+  /**
+   * Access the internal two-dimensional array.
+   * 
+   * @return Pointer to the two-dimensional array of matrix elements.
+   */
+
+  public double[][] getArray() {
+    return a;
+  }
+
+  /**
+   * Copy the internal two-dimensional array.
+   * 
+   * @return Two-dimensional array copy of matrix elements.
+   */
+
+  public double[][] getArrayCopy() {
+    double[][] x = new double[m][n];
+    for (int i = m; --i >= 0;)
+      for (int j = n; --j >= 0;)
+        x[i][j] = a[i][j];
+    return x;
+  }
+
+  /**
+   * Make a deep copy of a matrix
+   * 
+   * @return copy
+   */
+
+  public Matrix copy() {
+    Matrix x = new Matrix(null, m, n);
+    double[][] c = x.a;
+    for (int i = m; --i >= 0;)
+      for (int j = n; --j >= 0;)
+        c[i][j] = a[i][j];
+    return x;
+  }
+
+  /**
+   * Clone the Matrix object.
+   */
+
+  @Override
+  public Object clone() {
+    return copy();
+  }
+
+  /**
+   * Get a submatrix.
+   * 
+   * @param i0
+   *        Initial row index
+   * @param j0
+   *        Initial column index
+   * @param nrows
+   *        Number of rows
+   * @param ncols
+   *        Number of columns
+   * @return submatrix
+   * 
+   */
+
+  public Matrix getSubmatrix(int i0, int j0, int nrows, int ncols) {
+    Matrix x = new Matrix(null, nrows, ncols);
+    double[][] xa = x.a;
+    for (int i = nrows; --i >= 0;)
+      for (int j = ncols; --j >= 0;)
+        xa[i][j] = a[i0 + i][j0 + j];
+    return x;
+  }
+
+  /**
+   * Get a submatrix for a give number of columns and selected row set.
+   * 
+   * @param r
+   *        Array of row indices.
+   * @param n
+   *        number of rows 
+   * @return submatrix
+   */
+
+  public Matrix getMatrixSelected(int[] r, int n) {
+    Matrix x = new Matrix(null, r.length, n);
+    double[][] xa = x.a;
+    for (int i = r.length; --i >= 0;) {
+      double[] b = a[r[i]];
+      for (int j = n; --j >= 0;)
+        xa[i][j] = b[j];
+    }
+    return x;
+  }
+
+  /**
+   * Matrix transpose.
+   * 
+   * @return A'
+   */
+
+  public Matrix transpose() {
+    Matrix x = new Matrix(null, n, m);
+    double[][] c = x.a;
+    for (int i = m; --i >= 0;)
+      for (int j = n; --j >= 0;)
+        c[j][i] = a[i][j];
+    return x;
+  }
+
+  /**
+   * add two matrices
+   * @param b
+   * @return new Matrix this + b
+   */
+  public Matrix add(Matrix b) {
+    return scaleAdd(b, 1);
+  }
+
+  /**
+   * subtract two matrices
+   * @param b
+   * @return new Matrix this - b
+   */
+  public Matrix sub(Matrix b) {
+    return scaleAdd(b, -1);
+  }
+  
+  /**
+   * X = A + B*scale
+   * @param b 
+   * @param scale 
+   * @return X
+   * 
+   */
+  public Matrix scaleAdd(Matrix b, double scale) {
+    Matrix x = new Matrix(null, m, n);
+    double[][] xa = x.a;
+    double[][] ba = b.a;
+    for (int i = m; --i >= 0;)
+      for (int j = n; --j >= 0;)
+        xa[i][j] = ba[i][j] * scale + a[i][j];
+    return x;
+  }
+
+  /**
+   * Linear algebraic matrix multiplication, A * B
+   * 
+   * @param b
+   *        another matrix
+   * @return Matrix product, A * B or null for wrong dimension
+   */
+
+  public Matrix mul(Matrix b) {
+    if (b.m != n)
+      return null;
+    Matrix x = new Matrix(null, m, b.n);
+    double[][] xa = x.a;
+    double[][] ba = b.a;
+    for (int j = b.n; --j >= 0;)
+      for (int i = m; --i >= 0;) {
+        double[] arowi = a[i];
+        double s = 0;
+        for (int k = n; --k >= 0;)
+          s += arowi[k] * ba[k][j];
+        xa[i][j] = s;
+      }
+    return x;
+  }
+
+  /**
+   * Matrix inverse or pseudoinverse
+   * 
+   * @return inverse (m == n) or pseudoinverse (m != n)
+   */
+
+  public Matrix inverse() {
+    return new LUDecomp(m, n).solve(identity(m, m), n);
+  }
+
+  /**
+   * Matrix trace.
+   * 
+   * @return sum of the diagonal elements.
+   */
+
+  public double trace() {
+    double t = 0;
+    for (int i = Math.min(m, n); --i >= 0;)
+      t += a[i][i];
+    return t;
+  }
+
+  /**
+   * Generate identity matrix
+   * 
+   * @param m
+   *        Number of rows.
+   * @param n
+   *        Number of columns.
+   * @return An m-by-n matrix with ones on the diagonal and zeros elsewhere.
+   */
+
+  public static Matrix identity(int m, int n) {
+    Matrix x = new Matrix(null, m, n);
+    double[][] xa = x.a;
+    for (int i = Math.min(m, n); --i >= 0;)
+      xa[i][i] = 1;
+    return x;
+  }
+
+  /**
+   * similarly to M3/M4 standard rotation/translation matrix
+   * we set a rotationTranslation matrix to be:
+   * 
+   * [   nxn rot    nx1 trans
+   * 
+   *     1xn  0     1x1 1      ]
+   * 
+   * 
+   * @return rotation matrix
+   */
+  public Matrix getRotation() {
+    return getSubmatrix(0, 0, m - 1, n - 1);
+  }
+
+  public Matrix getTranslation() {
+    return getSubmatrix(0, n - 1, m - 1, 1);
+  }
+
+  public static Matrix newT(T3 r, boolean asColumn) {
+    return (asColumn ? new Matrix(new double[][] { new double[] { r.x },
+        new double[] { r.y }, new double[] { r.z } }, 3, 1) : new Matrix(
+        new double[][] { new double[] { r.x, r.y, r.z } }, 1, 3));
+  }
+
+  @Override
+  public String toString() {
+    String s = "[\n";
+    for (int i = 0; i < m; i++) {
+      s += "  [";
+      for (int j = 0; j < n; j++)
+        s += " " + a[i][j];
+      s += "]\n";
+    }
+    s += "]";
+    return s;
+  }
+
+  /**
+   * 
+   * Edited down by Bob Hanson for minimum needed by Jmol -- just constructor
+   * and solve
+   * 
+   * LU Decomposition.
+   * <P>
+   * For an m-by-n matrix A with m >= n, the LU decomposition is an m-by-n unit
+   * lower triangular matrix L, an n-by-n upper triangular matrix U, and a
+   * permutation vector piv of length m so that A(piv,:) = L*U. If m < n, then L
+   * is m-by-m and U is m-by-n.
+   * <P>
+   * The LU decompostion with pivoting always exists, even if the matrix is
+   * singular, so the constructor will never fail. The primary use of the LU
+   * decomposition is in the solution of square systems of simultaneous linear
+   * equations. This will fail if isNonsingular() returns false.
+   */
+
+  private class LUDecomp {
+
+    /* ------------------------
+       Class variables
+     * ------------------------ */
+
+    /**
+     * Array for internal storage of decomposition.
+     * 
+     */
+    private double[][] LU;
+
+    /**
+     * Internal storage of pivot vector.
+     * 
+     */
+    private int[] piv;
+
+    private int pivsign;
+
+    /* ------------------------
+       Constructor
+     * ------------------------ */
+
+    /**
+     * LU Decomposition Structure to access L, U and piv.
+     * @param m 
+     * @param n 
+     * 
+     */
+
+    protected LUDecomp(int m, int n) {
+      
+      // Use a "left-looking", dot-product, Crout/Doolittle algorithm.
+
+      LU = getArrayCopy();
+      piv = new int[m];
+      for (int i = m; --i >= 0;)
+        piv[i] = i;
+      pivsign = 1;
+      double[] LUrowi;
+      double[] LUcolj = new double[m];
+
+      // Outer loop.
+
+      for (int j = 0; j < n; j++) {
+
+        // Make a copy of the j-th column to localize references.
+
+        for (int i = m; --i >= 0;)
+          LUcolj[i] = LU[i][j];
+
+        // Apply previous transformations.
+
+        for (int i = m; --i >= 0;) {
+          LUrowi = LU[i];
+
+          // Most of the time is spent in the following dot product.
+
+          int kmax = Math.min(i, j);
+          double s = 0.0;
+          for (int k = kmax; --k >= 0;)
+            s += LUrowi[k] * LUcolj[k];
+
+          LUrowi[j] = LUcolj[i] -= s;
+        }
+
+        // Find pivot and exchange if necessary.
+
+        int p = j;
+        for (int i = m; --i > j;)
+          if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p]))
+            p = i;
+        if (p != j) {
+          for (int k = n; --k >= 0;) {
+            double t = LU[p][k];
+            LU[p][k] = LU[j][k];
+            LU[j][k] = t;
+          }
+          int k = piv[p];
+          piv[p] = piv[j];
+          piv[j] = k;
+          pivsign = -pivsign;
+        }
+
+        // Compute multipliers.
+
+        if (j < m & LU[j][j] != 0.0)
+          for (int i = m; --i > j;)
+            LU[i][j] /= LU[j][j];
+      }
+    }
+
+    /* ------------------------
+       default Methods
+     * ------------------------ */
+
+    /**
+     * Solve A*X = B
+     * 
+     * @param b
+     *        A Matrix with as many rows as A and any number of columns.
+     * @param n 
+     * @return X so that L*U*X = B(piv,:) or null for wrong size or singular matrix
+     */
+
+    protected Matrix solve(Matrix b, int n) {
+      for (int j = 0; j < n; j++)
+        if (LU[j][j] == 0)
+          return null; // matrix is singular
+
+      // Copy right hand side with pivoting
+      int nx = b.n;
+      Matrix x = b.getMatrixSelected(piv, nx);
+      double[][] a = x.a;
+
+      // Solve L*Y = B(piv,:)
+      for (int k = 0; k < n; k++)
+        for (int i = k + 1; i < n; i++)
+          for (int j = 0; j < nx; j++)
+            a[i][j] -= a[k][j] * LU[i][k];
+
+      // Solve U*X = Y;
+      for (int k = n; --k >= 0;) {
+        for (int j = nx; --j >= 0;)
+          a[k][j] /= LU[k][k];
+        for (int i = k; --i >= 0;)
+          for (int j = nx; --j >= 0;)
+            a[i][j] -= a[k][j] * LU[i][k];
+      }
+      return x;
+    }
+  }
+
+}
diff --git a/src/javajs/util/Measure.java b/src/javajs/util/Measure.java
new file mode 100644 (file)
index 0000000..8b21cfe
--- /dev/null
@@ -0,0 +1,728 @@
+/* $RCSfile$
+ * $Author: egonw $
+ * $Date: 2005-11-10 09:52:44 -0600 (Thu, 10 Nov 2005) $
+ * $Revision: 4255 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2003-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.util;
+
+import javajs.api.EigenInterface;
+
+import javajs.api.Interface;
+
+
+
+
+//import org.jmol.script.T;
+
+final public class Measure {
+
+  public final static float radiansPerDegree = (float) (2 * Math.PI / 360);
+  
+  public static float computeAngle(T3 pointA, T3 pointB, T3 pointC, V3 vectorBA, V3 vectorBC, boolean asDegrees) {
+    vectorBA.sub2(pointA, pointB);
+    vectorBC.sub2(pointC, pointB);
+    float angle = vectorBA.angle(vectorBC);
+    return (asDegrees ? angle / radiansPerDegree : angle);
+  }
+
+  public static float computeAngleABC(T3 pointA, T3 pointB, T3 pointC, boolean asDegrees) {
+    V3 vectorBA = new V3();
+    V3 vectorBC = new V3();        
+    return computeAngle(pointA, pointB, pointC, vectorBA, vectorBC, asDegrees);
+  }
+
+  public static float computeTorsion(T3 p1, T3 p2, T3 p3, T3 p4, boolean asDegrees) {
+  
+    float ijx = p1.x - p2.x;
+    float ijy = p1.y - p2.y;
+    float ijz = p1.z - p2.z;
+  
+    float kjx = p3.x - p2.x;
+    float kjy = p3.y - p2.y;
+    float kjz = p3.z - p2.z;
+  
+    float klx = p3.x - p4.x;
+    float kly = p3.y - p4.y;
+    float klz = p3.z - p4.z;
+  
+    float ax = ijy * kjz - ijz * kjy;
+    float ay = ijz * kjx - ijx * kjz;
+    float az = ijx * kjy - ijy * kjx;
+    float cx = kjy * klz - kjz * kly;
+    float cy = kjz * klx - kjx * klz;
+    float cz = kjx * kly - kjy * klx;
+  
+    float ai2 = 1f / (ax * ax + ay * ay + az * az);
+    float ci2 = 1f / (cx * cx + cy * cy + cz * cz);
+  
+    float ai = (float) Math.sqrt(ai2);
+    float ci = (float) Math.sqrt(ci2);
+    float denom = ai * ci;
+    float cross = ax * cx + ay * cy + az * cz;
+    float cosang = cross * denom;
+    if (cosang > 1) {
+      cosang = 1;
+    }
+    if (cosang < -1) {
+      cosang = -1;
+    }
+  
+    float torsion = (float) Math.acos(cosang);
+    float dot = ijx * cx + ijy * cy + ijz * cz;
+    float absDot = Math.abs(dot);
+    torsion = (dot / absDot > 0) ? torsion : -torsion;
+    return (asDegrees ? torsion / radiansPerDegree : torsion);
+  }
+
+  /**
+   * This method calculates measures relating to two points in space 
+   * with related quaternion frame difference. It is used in Jmol for
+   * calculating straightness and many other helical quantities.
+   * 
+   * @param a
+   * @param b
+   * @param dq
+   * @return  new T3[] { pt_a_prime, n, r, P3.new3(theta, pitch, residuesPerTurn), pt_b_prime };
+   */
+  public static T3[] computeHelicalAxis(P3 a, P3 b, Quat dq) {
+    
+    //                b
+    //           |   /|
+    //           |  / |
+    //           | /  |
+    //           |/   c
+    //         b'+   / \
+    //           |  /   \      Vcb = Vab . n
+    //         n | /     \d    Vda = (Vcb - Vab) / 2
+    //           |/theta  \
+    //         a'+---------a
+    //                r 
+
+    V3 vab = new V3();
+    vab.sub2(b, a);
+    /*
+     * testing here to see if directing the normal makes any difference -- oddly
+     * enough, it does not. When n = -n and theta = -theta vab.n is reversed,
+     * and that magnitude is multiplied by n in generating the A'-B' vector.
+     * 
+     * a negative angle implies a left-handed axis (sheets)
+     */
+    float theta = dq.getTheta();
+    V3 n = dq.getNormal();
+    float v_dot_n = vab.dot(n);
+    if (Math.abs(v_dot_n) < 0.0001f)
+      v_dot_n = 0;
+    V3 va_prime_d = new V3();
+    va_prime_d.cross(vab, n);
+    if (va_prime_d.dot(va_prime_d) != 0)
+      va_prime_d.normalize();
+    V3 vda = new V3();
+    V3 vcb = V3.newV(n);
+    if (v_dot_n == 0)
+      v_dot_n = PT.FLOAT_MIN_SAFE; // allow for perpendicular axis to vab
+    vcb.scale(v_dot_n);
+    vda.sub2(vcb, vab);
+    vda.scale(0.5f);
+    va_prime_d.scale(theta == 0 ? 0 : (float) (vda.length() / Math.tan(theta
+        / 2 / 180 * Math.PI)));
+    V3 r = V3.newV(va_prime_d);
+    if (theta != 0)
+      r.add(vda);
+    P3 pt_a_prime = P3.newP(a);
+    pt_a_prime.sub(r);
+    // already done this. ??
+    if (v_dot_n != PT.FLOAT_MIN_SAFE)
+      n.scale(v_dot_n);
+    // must calculate directed angle:
+    P3 pt_b_prime = P3.newP(pt_a_prime);
+    pt_b_prime.add(n);
+    theta = computeTorsion(a, pt_a_prime, pt_b_prime, b, true);
+    if (Float.isNaN(theta) || r.length() < 0.0001f)
+      theta = dq.getThetaDirectedV(n); // allow for r = 0
+    // anything else is an array
+    float residuesPerTurn = Math.abs(theta == 0 ? 0 : 360f / theta);
+    float pitch = Math.abs(v_dot_n == PT.FLOAT_MIN_SAFE ? 0 : n.length()
+        * (theta == 0 ? 1 : 360f / theta));
+    return new T3[] { pt_a_prime, n, r, P3.new3(theta, pitch, residuesPerTurn), pt_b_prime };
+  }
+
+  public static P4 getPlaneThroughPoints(T3 pointA,
+                                              T3 pointB,
+                                              T3 pointC, V3 vNorm,
+                                              V3 vAB, P4 plane) {
+    float w = getNormalThroughPoints(pointA, pointB, pointC, vNorm, vAB);
+    plane.set4(vNorm.x, vNorm.y, vNorm.z, w);
+    return plane;
+  }
+  
+  public static void getPlaneThroughPoint(T3 pt, V3 normal, P4 plane) {
+    plane.set4(normal.x, normal.y, normal.z, -normal.dot(pt));
+  }
+  
+  public static float distanceToPlane(P4 plane, T3 pt) {
+    return (plane == null ? Float.NaN 
+        : (plane.dot(pt) + plane.w) / (float) Math.sqrt(plane.dot(plane)));
+  }
+
+  public static float directedDistanceToPlane(P3 pt, P4 plane, P3 ptref) {
+    float f = plane.dot(pt) + plane.w;
+    float f1 = plane.dot(ptref) + plane.w;
+    return Math.signum(f1) * f /  (float) Math.sqrt(plane.dot(plane));
+  }
+
+  public static float distanceToPlaneD(P4 plane, float d, P3 pt) {
+    return (plane == null ? Float.NaN : (plane.dot(pt) + plane.w) / d);
+  }
+
+  public static float distanceToPlaneV(V3 norm, float w, P3 pt) {
+    return (norm == null ? Float.NaN 
+        : (norm.dot(pt) + w)  / (float) Math.sqrt(norm.dot(norm)));
+  }
+
+  /**
+   * note that if vAB or vAC is dispensible, vNormNorm can be one of them
+   * @param pointA
+   * @param pointB
+   * @param pointC
+   * @param vNormNorm
+   * @param vAB
+   */
+  public static void calcNormalizedNormal(T3 pointA, T3 pointB,
+         T3 pointC, T3 vNormNorm, T3 vAB) {
+    vAB.sub2(pointB, pointA);
+    vNormNorm.sub2(pointC, pointA);
+    vNormNorm.cross(vAB, vNormNorm);
+    vNormNorm.normalize();
+  }
+
+  public static float getDirectedNormalThroughPoints(T3 pointA, 
+         T3 pointB, T3 pointC, T3 ptRef, V3 vNorm, 
+         V3 vAB) {
+    // for x = plane({atomno=1}, {atomno=2}, {atomno=3}, {atomno=4})
+    float nd = getNormalThroughPoints(pointA, pointB, pointC, vNorm, vAB);
+    if (ptRef != null) {
+      P3 pt0 = P3.newP(pointA);
+      pt0.add(vNorm);
+      float d = pt0.distance(ptRef);
+      pt0.sub2(pointA, vNorm);
+      if (d > pt0.distance(ptRef)) {
+        vNorm.scale(-1);
+        nd = -nd;
+      }
+    }
+    return nd;
+  }
+  
+  /**
+   * @param pointA
+   * @param pointB
+   * @param pointC
+   * @param vNorm
+   * @param vTemp
+   * @return w
+   */
+  public static float getNormalThroughPoints(T3 pointA, T3 pointB,
+                                   T3 pointC, T3 vNorm, T3 vTemp) {
+    // for Polyhedra
+    calcNormalizedNormal(pointA, pointB, pointC, vNorm, vTemp);
+    // ax + by + cz + d = 0
+    // so if a point is in the plane, then N dot X = -d
+    vTemp.setT(pointA);
+    return -vTemp.dot(vNorm);
+  }
+
+  public static void getPlaneProjection(P3 pt, P4 plane, P3 ptProj, V3 vNorm) {
+    float dist = distanceToPlane(plane, pt);
+    vNorm.set(plane.x, plane.y, plane.z);
+    vNorm.normalize();
+    vNorm.scale(-dist);
+    ptProj.add2(pt, vNorm);
+  }
+
+  /**
+   * 
+   * @param ptCenter
+   * @param ptA
+   * @param ptB
+   * @param ptC
+   * @param isOutward
+   * @param normal set to be opposite to direction of ptCenter from ABC
+   * @param vTemp
+   * @return true if winding is CCW; false if CW
+   */
+  public static boolean getNormalFromCenter(P3 ptCenter, P3 ptA, P3 ptB, P3 ptC,
+                                      boolean isOutward, V3 normal, V3 vTemp) {
+    float d = getNormalThroughPoints(ptA, ptB, ptC, normal, vTemp);
+    boolean isReversed = (distanceToPlaneV(normal, d, ptCenter) > 0);
+    if (isReversed == isOutward)
+      normal.scale(-1f);
+    return !isReversed;
+  }
+
+  public final static V3 axisY = V3.new3(0, 1, 0);
+  
+  public static void getNormalToLine(P3 pointA, P3 pointB,
+                                   V3 vNormNorm) {
+    // vector in xy plane perpendicular to a line between two points RMH
+    vNormNorm.sub2(pointA, pointB);
+    vNormNorm.cross(vNormNorm, axisY);
+    vNormNorm.normalize();
+    if (Float.isNaN(vNormNorm.x))
+      vNormNorm.set(1, 0, 0);
+  }
+  
+  public static void getBisectingPlane(P3 pointA, V3 vAB,
+                                                 T3 ptTemp, V3 vTemp, P4 plane) {
+    ptTemp.scaleAdd2(0.5f, vAB, pointA);
+    vTemp.setT(vAB);
+    vTemp.normalize();
+    getPlaneThroughPoint(ptTemp, vTemp, plane);
+    }
+    
+  public static void projectOntoAxis(P3 point, P3 axisA,
+                                     V3 axisUnitVector,
+                                     V3 vectorProjection) {
+    vectorProjection.sub2(point, axisA);
+    float projectedLength = vectorProjection.dot(axisUnitVector);
+    point.scaleAdd2(projectedLength, axisUnitVector, axisA);
+    vectorProjection.sub2(point, axisA);
+  }
+  
+  public static void calcBestAxisThroughPoints(P3[] points, P3 axisA,
+                                               V3 axisUnitVector,
+                                               V3 vectorProjection,
+                                               int nTriesMax) {
+    // just a crude starting point.
+
+    int nPoints = points.length;
+    axisA.setT(points[0]);
+    axisUnitVector.sub2(points[nPoints - 1], axisA);
+    axisUnitVector.normalize();
+
+    /*
+     * We now calculate the least-squares 3D axis
+     * through the helix alpha carbons starting with Vo
+     * as a first approximation.
+     * 
+     * This uses the simple 0-centered least squares fit:
+     * 
+     * Y = M cross Xi
+     * 
+     * minimizing R^2 = SUM(|Y - Yi|^2) 
+     * 
+     * where Yi is the vector PERPENDICULAR of the point onto axis Vo
+     * and Xi is the vector PROJECTION of the point onto axis Vo
+     * and M is a vector adjustment 
+     * 
+     * M = SUM_(Xi cross Yi) / sum(|Xi|^2)
+     * 
+     * from which we arrive at:
+     * 
+     * V = Vo + (M cross Vo)
+     * 
+     * Basically, this is just a 3D version of a 
+     * standard 2D least squares fit to a line, where we would say:
+     * 
+     * y = m xi + b
+     * 
+     * D = n (sum xi^2) - (sum xi)^2
+     * 
+     * m = [(n sum xiyi) - (sum xi)(sum yi)] / D
+     * b = [(sum yi) (sum xi^2) - (sum xi)(sum xiyi)] / D
+     * 
+     * but here we demand that the line go through the center, so we
+     * require (sum xi) = (sum yi) = 0, so b = 0 and
+     * 
+     * m = (sum xiyi) / (sum xi^2)
+     * 
+     * In 3D we do the same but 
+     * instead of x we have Vo,
+     * instead of multiplication we use cross products
+     * 
+     * A bit of iteration is necessary.
+     * 
+     * Bob Hanson 11/2006
+     * 
+     */
+
+    calcAveragePointN(points, nPoints, axisA);
+
+    int nTries = 0;
+    while (nTries++ < nTriesMax
+        && findAxis(points, nPoints, axisA, axisUnitVector, vectorProjection) > 0.001) {
+    }
+
+    /*
+     * Iteration here gets the job done.
+     * We now find the projections of the endpoints onto the axis
+     * 
+     */
+
+    P3 tempA = P3.newP(points[0]);
+    projectOntoAxis(tempA, axisA, axisUnitVector, vectorProjection);
+    axisA.setT(tempA);
+  }
+
+  public static float findAxis(P3[] points, int nPoints, P3 axisA,
+                        V3 axisUnitVector, V3 vectorProjection) {
+    V3 sumXiYi = new V3();
+    V3 vTemp = new V3();
+    P3 pt = new P3();
+    P3 ptProj = new P3();
+    V3 a = V3.newV(axisUnitVector);
+
+    float sum_Xi2 = 0;
+    for (int i = nPoints; --i >= 0;) {
+      pt.setT(points[i]);
+      ptProj.setT(pt);
+      projectOntoAxis(ptProj, axisA, axisUnitVector,
+          vectorProjection);
+      vTemp.sub2(pt, ptProj);
+      //sum_Yi2 += vTemp.lengthSquared();
+      vTemp.cross(vectorProjection, vTemp);
+      sumXiYi.add(vTemp);
+      sum_Xi2 += vectorProjection.lengthSquared();
+    }
+    V3 m = V3.newV(sumXiYi);
+    m.scale(1 / sum_Xi2);
+    vTemp.cross(m, axisUnitVector);
+    axisUnitVector.add(vTemp);
+    axisUnitVector.normalize();  
+    //check for change in direction by measuring vector difference length
+    vTemp.sub2(axisUnitVector, a);
+    return vTemp.length();
+  }
+  
+  
+  public static void calcAveragePoint(P3 pointA, P3 pointB,
+                                      P3 pointC) {
+    pointC.set((pointA.x + pointB.x) / 2, (pointA.y + pointB.y) / 2,
+        (pointA.z + pointB.z) / 2);
+  }
+  
+  public static void calcAveragePointN(P3[] points, int nPoints,
+                                P3 averagePoint) {
+    averagePoint.setT(points[0]);
+    for (int i = 1; i < nPoints; i++)
+      averagePoint.add(points[i]);
+    averagePoint.scale(1f / nPoints);
+  }
+
+  public static Lst<P3> transformPoints(Lst<P3> vPts, M4 m4, P3 center) {
+    Lst<P3> v = new  Lst<P3>();
+    for (int i = 0; i < vPts.size(); i++) {
+      P3 pt = P3.newP(vPts.get(i));
+      pt.sub(center);
+      m4.rotTrans(pt);
+      pt.add(center);
+      v.addLast(pt);
+    }
+    return v;
+  }
+
+  public static boolean isInTetrahedron(P3 pt, P3 ptA, P3 ptB,
+                                        P3 ptC, P3 ptD,
+                                        P4 plane, V3 vTemp,
+                                        V3 vTemp2, boolean fullyEnclosed) {
+    boolean b = (distanceToPlane(getPlaneThroughPoints(ptC, ptD, ptA, vTemp, vTemp2, plane), pt) >= 0);
+    if (b != (distanceToPlane(getPlaneThroughPoints(ptA, ptD, ptB, vTemp, vTemp2, plane), pt) >= 0))
+      return false;
+    if (b != (distanceToPlane(getPlaneThroughPoints(ptB, ptD, ptC, vTemp, vTemp2, plane), pt) >= 0))
+      return false;
+    float d = distanceToPlane(getPlaneThroughPoints(ptA, ptB, ptC, vTemp, vTemp2, plane), pt);
+    if (fullyEnclosed)
+      return (b == (d >= 0));
+    float d1 = distanceToPlane(plane, ptD);
+    return d1 * d <= 0 || Math.abs(d1) > Math.abs(d);
+  }
+
+
+  /**
+   * 
+   * @param plane1
+   * @param plane2
+   * @return       [ point, vector ] or []
+   */
+  public static Lst<Object> getIntersectionPP(P4 plane1, P4 plane2) {
+    float a1 = plane1.x;
+    float b1 = plane1.y;
+    float c1 = plane1.z;
+    float d1 = plane1.w;
+    float a2 = plane2.x;
+    float b2 = plane2.y;
+    float c2 = plane2.z;
+    float d2 = plane2.w;
+    V3 norm1 = V3.new3(a1, b1, c1);
+    V3 norm2 = V3.new3(a2, b2, c2);
+    V3 nxn = new V3();
+    nxn.cross(norm1, norm2);
+    float ax = Math.abs(nxn.x);
+    float ay = Math.abs(nxn.y);
+    float az = Math.abs(nxn.z);
+    float x, y, z, diff;
+    int type = (ax > ay ? (ax > az ? 1 : 3) : ay > az ? 2 : 3);
+    switch(type) {
+    case 1:
+      x = 0;
+      diff = (b1 * c2 - b2 * c1);
+      if (Math.abs(diff) < 0.01) return null;
+      y = (c1 * d2 - c2 * d1) / diff;
+      z = (b2 * d1 - d2 * b1) / diff;
+      break;
+    case 2:
+      diff = (a1 * c2 - a2 * c1);
+      if (Math.abs(diff) < 0.01) return null;
+      x = (c1 * d2 - c2 * d1) / diff;
+      y = 0;
+      z = (a2 * d1 - d2 * a1) / diff;
+      break;
+    case 3:
+    default:
+      diff = (a1 * b2 - a2 * b1);
+      if (Math.abs(diff) < 0.01) return null;
+      x = (b1 * d2 - b2 * d1) / diff;
+      y = (a2 * d1 - d2 * a1) / diff;
+      z = 0;
+    }
+    Lst<Object>list = new  Lst<Object>();
+    list.addLast(P3.new3(x, y, z));
+    nxn.normalize();
+    list.addLast(nxn);
+    return list;
+  }
+
+  /**
+   * 
+   * @param pt1  point on line
+   * @param v    unit vector of line
+   * @param plane 
+   * @param ptRet  point of intersection of line with plane
+   * @param tempNorm 
+   * @param vTemp 
+   * @return       ptRtet
+   */
+  public static P3 getIntersection(P3 pt1, V3 v,
+                                               P4 plane, P3 ptRet, V3 tempNorm, V3 vTemp) {
+    getPlaneProjection(pt1, plane, ptRet, tempNorm);
+    tempNorm.set(plane.x, plane.y, plane.z);
+    tempNorm.normalize();
+    if (v == null)
+      v = V3.newV(tempNorm);
+    float l_dot_n = v.dot(tempNorm);
+    if (Math.abs(l_dot_n) < 0.01) return null;
+    vTemp.sub2(ptRet, pt1);
+    ptRet.scaleAdd2(vTemp.dot(tempNorm) / l_dot_n, v, pt1);
+    return ptRet;
+  }
+
+       /*
+        * public static Point3f getTriangleIntersection(Point3f a1, Point3f a2,
+        * Point3f a3, Point4f plane, Point3f b1, Point3f b2, Point3f b3, Vector3f
+        * vNorm, Vector3f vTemp, Point3f ptRet, Point3f ptTemp, Vector3f vTemp2,
+        * Point4f pTemp, Vector3f vTemp3) {
+        * 
+        * if (getTriangleIntersection(b1, b2, a1, a2, a3, vTemp, plane, vNorm,
+        * vTemp2, vTemp3, ptRet, ptTemp)) return ptRet; if
+        * (getTriangleIntersection(b2, b3, a1, a2, a3, vTemp, plane, vNorm, vTemp2,
+        * vTemp3, ptRet, ptTemp)) return ptRet; if (getTriangleIntersection(b3, b1,
+        * a1, a2, a3, vTemp, plane, vNorm, vTemp2, vTemp3, ptRet, ptTemp)) return
+        * ptRet; return null; }
+        */
+       /*
+        * public static boolean getTriangleIntersection(Point3f b1, Point3f b2,
+        * Point3f a1, Point3f a2, Point3f a3, Vector3f vTemp, Point4f plane, Vector3f
+        * vNorm, Vector3f vTemp2, Vector3f vTemp3, Point3f ptRet, Point3f ptTemp) {
+        * if (distanceToPlane(plane, b1) * distanceToPlane(plane, b2) >= 0) return
+        * false; vTemp.sub(b2, b1); vTemp.normalize(); if (getIntersection(b1, vTemp,
+        * plane, ptRet, vNorm, vTemp2) != null) { if (isInTriangle(ptRet, a1, a2, a3,
+        * vTemp, vTemp2, vTemp3)) return true; } return false; } private static
+        * boolean isInTriangle(Point3f p, Point3f a, Point3f b, Point3f c, Vector3f
+        * v0, Vector3f v1, Vector3f v2) { // from
+        * http://www.blackpawn.com/texts/pointinpoly/default.html // Compute
+        * barycentric coordinates v0.sub(c, a); v1.sub(b, a); v2.sub(p, a); float
+        * dot00 = v0.dot(v0); float dot01 = v0.dot(v1); float dot02 = v0.dot(v2);
+        * float dot11 = v1.dot(v1); float dot12 = v1.dot(v2); float invDenom = 1 /
+        * (dot00 * dot11 - dot01 * dot01); float u = (dot11 * dot02 - dot01 * dot12)
+        * * invDenom; float v = (dot00 * dot12 - dot01 * dot02) * invDenom; return (u
+        * > 0 && v > 0 && u + v < 1); }
+        */
+
+       /**
+        * Closed-form solution of absolute orientation requiring 1:1 mapping of
+        * positions.
+        * 
+        * @param centerAndPoints
+        * @param retStddev
+        * @return unit quaternion representation rotation
+        * 
+        * @author hansonr Bob Hanson
+        * 
+        */
+       public static Quat calculateQuaternionRotation(P3[][] centerAndPoints,
+                       float[] retStddev) {
+               /*
+                * see Berthold K. P. Horn,
+                * "Closed-form solution of absolute orientation using unit quaternions" J.
+                * Opt. Soc. Amer. A, 1987, Vol. 4, pp. 629-642
+                * http://www.opticsinfobase.org/viewmedia.cfm?uri=josaa-4-4-629&seq=0
+                * 
+                * 
+                * A similar treatment was developed independently (and later!) by G.
+                * Kramer, in G. R. Kramer,
+                * "Superposition of Molecular Structures Using Quaternions" Molecular
+                * Simulation, 1991, Vol. 7, pp. 113-119.
+                * 
+                * In that treatment there is a lot of unnecessary calculation along the
+                * trace of matrix M (eqn 20). I'm not sure why the extra x^2 + y^2 + z^2 +
+                * x'^2 + y'^2 + z'^2 is in there, but they are unnecessary and only
+                * contribute to larger numerical averaging errors and additional processing
+                * time, as far as I can tell. Adding aI, where a is a scalar and I is the
+                * 4x4 identity just offsets the eigenvalues but doesn't change the
+                * eigenvectors.
+                * 
+                * and Lydia E. Kavraki, "Molecular Distance Measures"
+                * http://cnx.org/content/m11608/latest/
+                */
+
+
+               retStddev[1] = Float.NaN;
+               Quat q = new Quat();
+               P3[] ptsA = centerAndPoints[0];
+               P3[] ptsB = centerAndPoints[1];
+               int nPts = ptsA.length - 1;
+               if (nPts < 2 || ptsA.length != ptsB.length)
+                       return q;
+               double Sxx = 0, Sxy = 0, Sxz = 0, Syx = 0, Syy = 0, Syz = 0, Szx = 0, Szy = 0, Szz = 0;
+               P3 ptA = new P3();
+               P3 ptB = new P3();
+               P3 ptA0 = ptsA[0];
+               P3 ptB0 = ptsB[0];
+               for (int i = nPts + 1; --i >= 1;) {
+                       ptA.sub2(ptsA[i], ptA0);
+                       ptB.sub2(ptsB[i], ptB0);
+                       Sxx += (double) ptA.x * (double) ptB.x;
+                       Sxy += (double) ptA.x * (double) ptB.y;
+                       Sxz += (double) ptA.x * (double) ptB.z;
+                       Syx += (double) ptA.y * (double) ptB.x;
+                       Syy += (double) ptA.y * (double) ptB.y;
+                       Syz += (double) ptA.y * (double) ptB.z;
+                       Szx += (double) ptA.z * (double) ptB.x;
+                       Szy += (double) ptA.z * (double) ptB.y;
+                       Szz += (double) ptA.z * (double) ptB.z;
+               }
+               retStddev[0] = getRmsd(centerAndPoints, q);
+               double[][] N = new double[4][4];
+               N[0][0] = Sxx + Syy + Szz;
+               N[0][1] = N[1][0] = Syz - Szy;
+               N[0][2] = N[2][0] = Szx - Sxz;
+               N[0][3] = N[3][0] = Sxy - Syx;
+
+               N[1][1] = Sxx - Syy - Szz;
+               N[1][2] = N[2][1] = Sxy + Syx;
+               N[1][3] = N[3][1] = Szx + Sxz;
+
+               N[2][2] = -Sxx + Syy - Szz;
+               N[2][3] = N[3][2] = Syz + Szy;
+
+               N[3][3] = -Sxx - Syy + Szz;
+
+               // this construction prevents JavaScript from requiring preloading of Eigen
+
+               float[] v = ((EigenInterface) Interface.getInterface("javajs.util.Eigen"))
+                               .setM(N).getEigenvectorsFloatTransposed()[3];
+               q = Quat.newP4(P4.new4(v[1], v[2], v[3], v[0]));
+               retStddev[1] = getRmsd(centerAndPoints, q);
+               return q;
+       }
+
+  /**
+   * Fills a 4x4 matrix with rotation-translation of mapped points A to B.
+   * If centerA is null, this is a standard 4x4 rotation-translation matrix;
+   * otherwise, this 4x4 matrix is a rotation around a vector through the center of ptsA,
+   * and centerA is filled with that center; 
+   * Prior to Jmol 14.3.12_2014.02.14, when used from the JmolScript compare() function,
+   * this method returned the second of these options instead of the first.
+   * 
+   * @param ptsA
+   * @param ptsB
+   * @param m  4x4 matrix to be returned 
+   * @param centerA return center of rotation; if null, then standard 4x4 matrix is returned
+   * @return stdDev
+   */
+  public static float getTransformMatrix4(Lst<P3> ptsA, Lst<P3> ptsB, M4 m,
+                                          P3 centerA) {
+    P3[] cptsA = getCenterAndPoints(ptsA);
+    P3[] cptsB = getCenterAndPoints(ptsB);
+    float[] retStddev = new float[2];
+    Quat q = calculateQuaternionRotation(new P3[][] { cptsA, cptsB },
+        retStddev);
+    M3 r = q.getMatrix();
+    if (centerA == null)
+      r.rotate(cptsA[0]);
+    else
+      centerA.setT(cptsA[0]);
+    V3 t = V3.newVsub(cptsB[0], cptsA[0]);
+    m.setMV(r, t);
+    return retStddev[1];
+  }
+
+  /**
+   * from a list of points, create an array that includes the center
+   * point as the first point. This array is used as a starting point for
+   * a quaternion analysis of superposition.
+   * 
+   * @param vPts
+   * @return  array of points with first point center
+   */
+       public static P3[] getCenterAndPoints(Lst<P3> vPts) {
+         int n = vPts.size();
+         P3[] pts = new P3[n + 1];
+         pts[0] = new P3();
+         if (n > 0) {
+           for (int i = 0; i < n; i++) {
+             pts[0].add(pts[i + 1] = vPts.get(i));
+           }
+           pts[0].scale(1f / n);
+         }
+         return pts;
+       }
+
+  public static float getRmsd(P3[][] centerAndPoints, Quat q) {
+    double sum2 = 0;
+    P3[] ptsA = centerAndPoints[0];
+    P3[] ptsB = centerAndPoints[1];
+    P3 cA = ptsA[0];
+    P3 cB = ptsB[0];
+    int n = ptsA.length - 1;
+    P3 ptAnew = new P3();
+    
+    for (int i = n + 1; --i >= 1;) {
+      ptAnew.sub2(ptsA[i], cA);
+      q.transform2(ptAnew, ptAnew).add(cB);
+      sum2 += ptAnew.distanceSquared(ptsB[i]);
+    }
+    return (float) Math.sqrt(sum2 / n);
+  }
+
+}
diff --git a/src/javajs/util/MessagePackReader.java b/src/javajs/util/MessagePackReader.java
new file mode 100644 (file)
index 0000000..a0e6b8b
--- /dev/null
@@ -0,0 +1,716 @@
+package javajs.util;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javajs.api.GenericBinaryDocumentReader;
+
+/**
+ * A simple MessagePack reader. See https://github.com/msgpack/msgpack/blob/master/spec.md
+ * with very few dependencies.
+ * 
+ * Nuances: 
+ * 
+ *  Does not implement unsigned int32 or int64 (delivers simple integers in all cases).
+ *  Does not use doubles; just floats
+ *  
+ * Note: 
+ * 
+ *  homogeneousArrays == true will deliver null for empty array.
+ * 
+ * 
+ * Use in MMTF:
+ * 
+ * 
+ *     BufferedInputStream bs = [whatever]
+ *     
+ *      GenericBinaryDocument binaryDoc =  new javajs.util.BinaryDocument();
+ *   
+ *      binaryDoc.setStream(bs, true);
+ * 
+ * 
+ *     map = (new MessagePackReader(binaryDoc, true)).readMap();
+ * 
+ *     entities = (Object[]) map.get("entityList");
+ *
+ *     float[] x = (float[]) decode((byte[]) map.get("xCoordList"))
+ *     
+ * 
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+
+public class MessagePackReader {
+
+  private GenericBinaryDocumentReader doc;
+
+  private boolean isHomo;// homogeneous arrays -- use int[] not Integer
+
+  // these maps must be checked for the specific number of bits, in the following order:
+  private final static int POSITIVEFIXINT_x80 = 0x80; //0xxxxxxx
+  private final static int FIXMAP_xF0         = 0x80; //1000xxxx
+//  private final static int FIXARRAY_xF0       = 0x90; //1001xxxx
+  private final static int FIXSTR_xE0         = 0xa0; //101xxxxx
+  private final static int NEGATIVEFIXINT_xE0 = 0xe0; //111xxxxx
+  private final static int DEFINITE_xE0       = 0xc0; //110xxxxx
+  
+  private final static int NIL          = 0xc0;
+//  private final static int (NEVERUSED)        = 0xc1;
+  private final static int FALSE        = 0xc2;
+  private final static int TRUE         = 0xc3;
+  private final static int BIN8         = 0xc4;
+  private final static int BIN16        = 0xc5;
+  private final static int BIN32        = 0xc6;
+  private final static int EXT8         = 0xc7;
+  private final static int EXT16        = 0xc8;
+  private final static int EXT32        = 0xc9;
+  private final static int FLOAT32      = 0xca;
+  private final static int FLOAT64      = 0xcb;
+  private final static int UINT8        = 0xcc;
+  private final static int UINT16       = 0xcd;
+  private final static int UINT32       = 0xce;
+  private final static int UINT64       = 0xcf;
+  private final static int INT8         = 0xd0;
+  private final static int INT16        = 0xd1;
+  private final static int INT32        = 0xd2;
+  private final static int INT64        = 0xd3;
+  private final static int FIXEXT1      = 0xd4;
+  private final static int FIXEXT2      = 0xd5;
+  private final static int FIXEXT4      = 0xd6;
+  private final static int FIXEXT8      = 0xd7;
+  private final static int FIXEXT16     = 0xd8;
+  private final static int STR8         = 0xd9;
+  private final static int STR16        = 0xda;
+  private final static int STR32        = 0xdb;
+  private final static int ARRAY16      = 0xdc;
+  private final static int ARRAY32      = 0xdd;
+  private final static int MAP16        = 0xde;
+  private final static int MAP32        = 0xdf;
+
+  public MessagePackReader(GenericBinaryDocumentReader binaryDoc, boolean isHomogeneousArrays) {
+    isHomo = isHomogeneousArrays;
+    doc = binaryDoc;
+  }
+
+  public MessagePackReader() {
+    // for reflection
+  }
+  
+  public Map<String, Object> getMapForStream(BufferedInputStream is) throws Exception {
+    doc = new BinaryDocument().setStream(is, true);
+    Map<String, Object> map = readMap();
+    is.close();
+    return map;
+  }
+  
+  @SuppressWarnings("unchecked")
+  public Map<String, Object> readMap() throws Exception {
+    return (Map<String, Object>) getNext(null, 0);
+  }
+  
+  public Object getNext(Object array, int pt) throws Exception {
+    int b = doc.readByte() & 0xFF;
+    int be0 = b & 0xE0;
+    if ((b & POSITIVEFIXINT_x80) == 0) {
+      if (array != null) {
+        ((int[]) array)[pt] = b;
+        return null;
+      }
+      return Integer.valueOf(b);
+    }
+    switch (be0) {
+    case NEGATIVEFIXINT_xE0:
+      b = BC.intToSignedInt(b | 0xFFFFFF00);
+      if (array != null) {
+        ((int[]) array)[pt] = b;
+        return null;
+      }
+      return Integer.valueOf(b);
+    case FIXSTR_xE0: {
+      String s = doc.readString(b & 0x1F);
+      if (array != null) {
+        ((String[]) array)[pt] = s; 
+        return null;
+      } 
+      return s;
+    }
+    case FIXMAP_xF0:
+      return ((b & 0xF0) == FIXMAP_xF0 ? getMap(b & 0x0F) : getArray(b & 0x0F));
+    case DEFINITE_xE0:
+      switch (b) {
+      case NIL:
+        return null;
+      case FALSE:
+        return Boolean.FALSE;
+      case TRUE:
+        return Boolean.TRUE;
+      case EXT8:
+        return getObject(doc.readUInt8());
+      case EXT16:
+        return getObject(doc.readUnsignedShort());
+      case EXT32:
+        return getObject(doc.readInt()); // should be unsigned int
+      case FIXEXT1:
+        return getObject(1);
+      case FIXEXT2:
+        return getObject(2);
+      case FIXEXT4:
+        return getObject(4);
+      case FIXEXT8:
+        return getObject(8);
+      case FIXEXT16:
+        return getObject(16);
+      case ARRAY16:
+        return getArray(doc.readUnsignedShort());
+      case ARRAY32:
+        return getArray(doc.readInt());
+      case MAP16:
+        return getMap(doc.readUnsignedShort());
+      case MAP32:
+        return getMap(doc.readInt());
+
+        // binary arrays:
+
+      case BIN8:
+        return doc.readBytes(doc.readUInt8());
+      case BIN16:
+        return doc.readBytes(doc.readUnsignedShort());
+      case BIN32:
+        return doc.readBytes(doc.readInt());
+      }
+      if (array == null) {
+        switch (b) {
+        case FLOAT32:
+          return Float.valueOf(doc.readFloat());
+        case FLOAT64:
+          return Float.valueOf((float) doc.readDouble());
+        case UINT8:
+          return Integer.valueOf(doc.readUInt8());
+        case UINT16:
+          return Integer.valueOf(doc.readUnsignedShort());
+        case UINT32:
+          return Integer.valueOf(doc.readInt()); // technically should be UInt32
+        case UINT64:
+          return Long.valueOf(doc.readLong()); // should be unsigned long; incompatible with JavaScript!
+        case INT8:
+          return Integer.valueOf(doc.readByte());
+        case INT16:
+          return Integer.valueOf(doc.readShort());
+        case INT32:
+          return Integer.valueOf(doc.readInt()); // should be Unsigned Int here
+        case INT64:
+          return Long.valueOf(doc.readLong());
+        case STR8:
+          return doc.readString(doc.readUInt8());
+        case STR16:
+          return doc.readString(doc.readShort());
+        case STR32:
+          return doc.readString(doc.readInt());
+        }
+      } else {
+        switch (b) {
+        case FLOAT32:
+          ((float[]) array)[pt] = doc.readFloat();
+          break;
+        case FLOAT64:
+          ((float[]) array)[pt] = (float) doc.readDouble();
+          break;
+        case UINT8:
+          ((int[]) array)[pt] = doc.readUInt8();
+          break;
+        case UINT16:
+          ((int[]) array)[pt] = doc.readUnsignedShort();
+          break;
+        case UINT32:
+          ((int[]) array)[pt] =  doc.readInt(); // should be unsigned int
+          break;
+        case UINT64:
+          ((int[]) array)[pt] =  (int) doc.readLong(); // should be unsigned long; incompatible with JavaScript!
+          break;
+        case INT8:
+          ((int[]) array)[pt] =  doc.readByte();
+          break;
+        case INT16:
+          ((int[]) array)[pt] = doc.readShort();
+          break;
+        case INT32:
+          ((int[]) array)[pt] =  doc.readInt(); // should be Unsigned Int here
+          break;
+        case INT64:
+          ((int[]) array)[pt] =  (int) doc.readLong();
+          break;
+        case STR8:
+          ((String[]) array)[pt] = doc.readString(doc.readUInt8());
+          break;
+        case STR16:
+          ((String[]) array)[pt] = doc.readString(doc.readShort());
+          break;
+        case STR32:
+          ((String[]) array)[pt] = doc.readString(doc.readInt());
+          break;
+        }
+      }
+    }
+    return null;
+  }
+
+  private Object getObject(int n) throws Exception {
+    return new Object[] { Integer.valueOf(doc.readUInt8()), doc.readBytes(n) };
+  }
+
+  private Object getArray(int n) throws Exception {
+    if (isHomo) {
+      if (n == 0)
+        return null;
+      Object v = getNext(null, 0);
+      if (v instanceof Integer) {
+        int[] a = new int[n];
+        a[0] = ((Integer) v).intValue();
+        v = a;
+      } else if (v instanceof Float) {
+        float[] a = new float[n];
+        a[0] = ((Float) v).floatValue();
+        v = a;
+      } else if (v instanceof String) {
+        String[] a = new String[n];
+        a[0] = (String) v;
+        v = a;
+      } else {
+        Object[] o = new Object[n];
+        o[0] = v;
+        for (int i = 1; i < n; i++)
+          o[i] = getNext(null, 0);
+        return o;
+      }
+      for (int i = 1; i < n; i++)
+        getNext(v, i);
+      return v;
+    }
+    Object[] o = new Object[n];
+    for (int i = 0; i < n; i++)
+      o[i] = getNext(null, 0);
+    return o;
+  }
+
+  private Object getMap(int n) throws Exception {
+    Map<String, Object> map = new Hashtable<String, Object>();
+    for (int i = 0; i < n; i++) {
+      String key = getNext(null, 0).toString();
+      //Logger.info(key);
+
+      Object value = getNext(null, 0);
+      if (value == null) {
+        //Logger.info("null value for " + key);
+      } else {
+        map.put(key, value);
+      }
+    }
+    return map;
+  }
+
+  /////////////// MMTF MessagePack decoding ///////////////
+
+  /**
+   * This single method takes care of all MMTF needs.
+   * 
+   * See https://github.com/rcsb/mmtf/blob/master/spec.md
+   * 
+   * @param b
+   * 
+   * @return array of int, char, or float, depending upon the type
+   */
+  public static Object decode(byte[] b) {
+    int type = BC.bytesToInt(b, 0, true);
+    int n = BC.bytesToInt(b, 4, true);
+    int param = BC.bytesToInt(b, 8, true);
+    switch (type) {
+    case 1:
+      return getFloats(b, n, 1);
+    case 2: // 1-byte
+    case 3: // 2-byte
+    case 4: // 4-byte
+      return getInts(b, n);
+    case 5:
+      return rldecode32ToStr(b);
+    case 6:
+      return rldecode32ToChar(b, n);
+    case 7:
+      return rldecode32(b, n);
+    case 8:
+      return rldecode32Delta(b, n);
+    case 9:
+      return rldecodef(b, n, param);
+    case 10:
+      return unpack16Deltaf(b, n, param);
+    case 11:
+      return getFloats(b, n, param);
+    case 12: // two-byte
+    case 13: // one-byte
+      return unpackf(b, 14 - type, n, param);
+    case 14: // two-byte
+    case 15: // one-byte
+      return unpack(b, 16 - type, n);
+    default:
+      System.out.println("MMTF type " + type + " not found!");
+      return null;
+   }
+  }
+
+  /**
+   * mmtf type 1 and 11
+   * 
+   * byte[4] to float32
+   * 
+   * @param b
+   * @param n
+   * @param divisor
+   * @return array of floats
+   */
+  public static float[] getFloats(byte[] b, int n, float divisor) {
+    if (b == null)
+      return null;
+    float[] a = new float[n];
+    try {
+      switch ((b.length - 12) / n) {  
+      case 2:
+        for (int i = 0, j = 12; i < n; i++, j += 2)
+          a[i] = BC.bytesToShort(b, j, false) / divisor;
+        break;
+      case 4:
+        for (int i = 0, j = 12; i < n; i++, j += 4)
+          a[i] = BC.bytesToFloat(b, j, false);
+        break;
+      }
+    } catch (Exception e) {
+    }
+    return a;
+  }
+
+  /**
+   * mmtf types 2-4
+   * 
+   * Decode a byte array into a byte, short, or int array.
+   * 
+   * @param b
+   * @param n
+   *        
+   * @return array of integers
+   */
+  public static int[] getInts(byte[] b, int n) {
+    if (b == null)
+      return null;
+    int[] a = new int[n];
+    switch ((b.length - 12) / n) {
+    case 1:
+      for (int i = 0, j = 12; i < n; i++, j++)
+        a[i] = b[j];
+      break;
+    case 2:
+      for (int i = 0, j = 12; i < n; i++, j += 2)
+        a[i] = BC.bytesToShort(b, j, true);
+      break;
+    case 4:
+      for (int i = 0, j = 12; i < n; i++, j += 4)
+        a[i] = BC.bytesToInt(b, j, true);
+      break;
+    }
+    return a;
+  }
+
+  /**
+   * mmtf type 5
+   * 
+   * Decode each four bytes as a 1- to 4-character string label where a 0 byte
+   * indicates end-of-string.
+   * 
+   * @param b
+   *        a byte array
+   * @return String[]
+   */
+  public static String[] rldecode32ToStr(byte[] b) {
+    String[] id = new String[(b.length - 12) / 4];
+    out: for (int i = 0, len = id.length, pt = 12; i < len; i++) {
+      SB sb = new SB();
+      for (int j = 0; j < 4; j++) {
+        switch (b[pt]) {
+        case 0:
+          id[i] = sb.toString();
+          pt += 4 - j;
+          continue out;
+        default:
+          sb.appendC((char) b[pt++]);
+          if (j == 3)
+            id[i] = sb.toString();
+          continue;
+        }
+      }
+    }
+    return id;
+  }
+
+  /**
+   * mmtf type 6
+   * 
+   * Decode an array of int32 using run-length decoding to one char per int.
+   * 
+   * @param b
+   * @param n
+   * @return array of characters
+   */
+  public static char[] rldecode32ToChar(byte[] b, int n) {
+    if (b == null)
+      return null;
+    char[] ret = new char[n];
+    for (int i = 0, pt = 3; i < n;) {
+      char val = (char) b[((pt++) << 2) + 3];
+      for (int j = BC.bytesToInt(b, (pt++) << 2, true); --j >= 0;)
+        ret[i++] = val;
+    }
+    return ret;
+  }
+
+  /**
+   * mmtf type 7
+   * 
+   * Decode an array of int32 using run-length decoding.
+   * 
+   * @param b
+   * @param n
+   * @return array of integers
+   */
+  public static int[] rldecode32(byte[] b, int n) {
+    if (b == null)
+      return null;
+    int[] ret = new int[n];
+    for (int i = 0, pt = 3; i < n;) {
+      int val = BC.bytesToInt(b, (pt++) << 2, true);
+      for (int j = BC.bytesToInt(b, (pt++) << 2, true); --j >= 0;)
+        ret[i++] = val;
+    }
+    return ret;
+  }
+
+  /**
+   * mmtf type 8
+   * 
+   * Decode an array of int32 using run-length decoding of a difference array.
+   * 
+   * @param b
+   * @param n
+   * @return array of integers
+   */
+  public static int[] rldecode32Delta(byte[] b, int n) {
+    if (b == null)
+      return null;
+    int[] ret = new int[n];
+    for (int i = 0, pt = 3, val = 0; i < n;) {
+      int diff = BC.bytesToInt(b, (pt++) << 2, true);
+      for (int j = BC.bytesToInt(b, (pt++) << 2, true); --j >= 0;)
+        ret[i++] = (val = val + diff);
+    }
+    return ret;
+  }
+
+  /**
+   * mmtf type 9
+   * 
+   * Decode an array of int32 using run-length decoding and divide by a divisor
+   * to give a float32.
+   * 
+   * @param b
+   * @param n
+   * @param divisor
+   * @return array of floats
+   */
+  public static float[] rldecodef(byte[] b, int n, float divisor) {
+    if (b == null)
+      return null;
+    float[] ret = new float[n];
+    for (int i = 0, pt = 3; i < n;) {
+      int val = BC.bytesToInt(b, (pt++) << 2, true);
+      for (int j = BC.bytesToInt(b, (pt++) << 2, true); --j >= 0;)
+        ret[i++] = val / divisor;
+    }
+    return ret;
+  }
+
+  /**
+   * 
+   * mmtf type 10
+   * 
+   * Decode an array of int16 using run-length decoding of a difference array.
+   * 
+   * @param b
+   * @param n
+   * @param divisor
+   * @return array of floats
+   */
+  public static float[] unpack16Deltaf(byte[] b, int n, float divisor) {
+    if (b == null)
+      return null;
+    float[] ret = new float[n];
+    for (int i = 0, pt = 6, val = 0, buf = 0; i < n;) {
+      int diff = BC.bytesToShort(b, (pt++) << 1, true);
+      if (diff == Short.MAX_VALUE || diff == Short.MIN_VALUE) {
+        buf += diff;
+      } else {
+        ret[i++] = (val = val + diff + buf) / divisor;
+        buf = 0;
+      }
+    }
+    return ret;
+  }
+
+  /**
+   * 
+   * mmtf type 12 and 13
+   * 
+   * Unpack an array of int8 or int16 to int32 and divide to give a float32.
+   * 
+   * untested
+   * 
+   * @param b
+   * @param nBytes 
+   * @param n
+   * @param divisor 
+   * @return array of floats
+   */
+  public static float[] unpackf(byte[] b, int nBytes, int n, float divisor) {
+    if (b == null)
+      return null;
+    float[] ret = new float[n];
+    switch (nBytes) {
+    case 1:
+      for (int i = 0, pt = 12, offset = 0; i < n;) {
+        int val = b[pt++];
+        if (val == Byte.MAX_VALUE || val == Byte.MIN_VALUE) {
+          offset += val;
+        } else {
+          ret[i++] = (val + offset) / divisor;
+          offset = 0;
+        }
+      }
+      break;
+    case 2:
+      for (int i = 0, pt = 6, offset = 0; i < n;) {
+        int val = BC.bytesToShort(b, (pt++) << 1, true);
+        if (val == Short.MAX_VALUE || val == Short.MIN_VALUE) {
+          offset += val;
+        } else {
+          ret[i++] = (val + offset) / divisor;
+          offset = 0;
+        }
+      }
+      break;
+    }
+    return ret;
+  }
+
+  /**
+   * 
+   * mmtf type 14 and 15
+   * 
+   * Unpack an array of int8 or int16 to int32.
+   * 
+   * untested
+   * 
+   * @param b
+   * @param nBytes 
+   * @param n
+   * @return array of integers
+   */
+  public static int[] unpack(byte[] b, int nBytes, int n) {
+    if (b == null)
+      return null;
+    int[] ret = new int[n];
+    switch (nBytes) {
+    case 1:
+      for (int i = 0, pt = 12, offset = 0; i < n;) {
+        int val = b[pt++];
+        if (val == Byte.MAX_VALUE || val == Byte.MIN_VALUE) {
+          offset += val;
+        } else {
+          ret[i++] = val + offset;
+          offset = 0;
+        }
+      }
+      break;
+    case 2:
+      for (int i = 0, pt = 6, offset = 0; i < n;) {
+        int val = BC.bytesToShort(b, (pt++) << 1, true);
+        if (val == Short.MAX_VALUE || val == Short.MIN_VALUE) {
+          offset += val;
+        } else {
+          ret[i++] = val + offset;
+          offset = 0;
+        }
+      }
+      break;
+    }
+    return ret;
+  }
+
+  ///**
+  //* Decode an array of int16 using run-length decoding
+  //* of a difference array.
+  //* 
+  //* @param b
+  //* @param n
+  //* @param i0 
+  //* @return array of integers
+  //*/
+  //public static int[] rldecode16Delta(byte[] b, int n, int i0) {
+  //if (b == null)
+  //return null;
+  //int[] ret = new int[n];
+  //for (int i = 0, pt = i0 / 2, val = 0; i < n;) {
+  //int diff = BC.bytesToShort(b, (pt++) << 1, true);
+  //for (int j = BC.bytesToShort(b, (pt++) << 1, true); --j >= 0;)
+  //ret[i++] = (val = val + diff);
+  //}
+  //return ret;
+  //}
+
+  ///**
+  //* Do a split delta to a float[] array
+  //* @param xyz label "x", "y", "z", or "bFactor"
+  //* @param factor for dividing in the end -- 1000f or 100f 
+  //* @return float[]
+  //* 
+  //*/ 
+  //public static float[] getFloatsSplit(String xyz, float factor) {
+  //byte[] big = (byte[]) map.get(xyz + "Big");
+  //return (big == null ? null : splitDelta(big,
+  //(byte[]) map.get(xyz + "Small"), fileAtomCount, factor));
+  //}
+
+  ///**
+  //* Do a split delta to a float[] array
+  //* 
+  //* @param big
+  //*        [n m n m n m...] where n is a "big delta" and m is a number of
+  //*        "small deltas
+  //* @param small
+  //*        array containing the small deltas
+  //* @param n
+  //*        the size of the final array
+  //* @param factor
+  //*        to divide the final result by -- 1000f or 100f here
+  //* @return float[]
+  //*/
+  //public static float[] splitDelta(byte[] big, byte[] small, int n, float factor) {
+  //float[] ret = new float[n];
+  //for (int i = 0, smallpt = 0, val = 0, datapt = 0, len = big.length >> 2; i < len; i++) {
+  //ret[datapt++] = (val = val + BC.bytesToInt(big, i << 2, true)) / factor;
+  //if (++i < len)
+  //for (int j = BC.bytesToInt(big, i << 2, true); --j >= 0; smallpt++)
+  //ret[datapt++] = (val = val + BC.bytesToShort(small, smallpt << 1, true))
+  //  / factor;
+  //}
+  //return ret;
+  //}
+  //
+
+
+}
diff --git a/src/javajs/util/OC.java b/src/javajs/util/OC.java
new file mode 100644 (file)
index 0000000..98151d1
--- /dev/null
@@ -0,0 +1,416 @@
+package javajs.util;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javajs.api.BytePoster;
+import javajs.api.GenericOutputChannel;
+import javajs.api.js.J2SObjectInterface;
+
+/**
+ * 
+ * A generic output method. JmolOutputChannel can be used to:
+ * 
+ * add characters to a StringBuffer 
+ *   using fileName==null, append() and toString()
+ *   
+ * add bytes utilizing ByteArrayOutputStream 
+ *   using writeBytes(), writeByteAsInt(), append()*, and bytesAsArray()
+ *       *append() can be used as long as os==ByteArrayOutputStream
+ *        or it is not used before one of the writeByte methods. 
+ * 
+ * output characters to a FileOutputStream 
+ *  using os==FileOutputStream, asWriter==true, append(), and closeChannel()
+ *  
+ * output bytes to a FileOutputStream 
+ *  using os==FileOutputStream, writeBytes(), writeByteAsInt(), append(), and closeChannel()
+ * 
+ * post characters or bytes to a remote server
+ *  using fileName=="http://..." or "https://...",
+ *    writeBytes(), writeByteAsInt(), append(), and closeChannel()
+ *    
+ * send characters or bytes to a JavaScript function
+ *  when JavaScript and (typeof fileName == "function")
+ *  
+ * if fileName equals ";base64,", then the data are base64-encoded
+ * prior to writing, and closeChannel() returns the data.
+ * 
+ *  @author hansonr  Bob Hanson hansonr@stolaf.edu  9/2013
+ *  
+ *  
+ */
+
+public class OC extends OutputStream implements GenericOutputChannel {
+  private BytePoster bytePoster; // only necessary for writing to http:// or https://
+  private String fileName;
+  private BufferedWriter bw;
+  private boolean isLocalFile;
+  private int byteCount;
+  private boolean isCanceled;
+  private boolean closed;
+  private OutputStream os;
+  private SB sb;
+  private String type;
+       private boolean isBase64;
+       private OutputStream os0;
+       private byte[] bytes; // preset bytes; output only
+  
+       public boolean bigEndian = true;
+  
+  @Override
+  public boolean isBigEndian() {
+    return bigEndian;
+  }
+
+  public void setBigEndian(boolean TF) {
+       bigEndian = TF;
+  }
+
+  public OC setParams(BytePoster bytePoster, String fileName,
+                                     boolean asWriter, OutputStream os) {
+    this.bytePoster = bytePoster;
+    isBase64 = ";base64,".equals(fileName);
+    if (isBase64) {
+       fileName = null;
+       os0 = os;
+       os = null;
+    }
+    this.fileName = fileName;
+    this.os = os;
+    isLocalFile = (fileName != null && !isRemote(fileName));
+    if (asWriter && !isBase64 && os != null) 
+       bw = Rdr.getBufferedWriter(os, null);
+    return this;
+  }
+
+  public OC setBytes(byte[] b) {
+       bytes = b;
+       return this;
+  }
+  
+  public String getFileName() {
+    return fileName;
+  }
+  
+  public String getName() {
+    return (fileName == null ? null : fileName.substring(fileName.lastIndexOf("/") + 1));
+  }
+
+  public int getByteCount() {
+    return byteCount;
+  }
+
+  /**
+   * 
+   * @param type  user-identified type (PNG, JPG, etc)
+   */
+  public void setType(String type) {
+    this.type = type;
+  }
+  
+  public String getType() {
+    return type;
+  }
+
+  /**
+   * will go to string buffer if bw == null and os == null
+   * 
+   * @param s
+   * @return this, for chaining like a standard StringBuffer
+   * 
+   */
+  public OC append(String s) {
+    try {
+      if (bw != null) {
+        bw.write(s);
+      } else if (os == null) {
+        if (sb == null)
+          sb = new SB();
+        sb.append(s);
+      } else {
+        byte[] b = s.getBytes();
+        os.write(b, 0, b.length);
+        byteCount += b.length;
+        return this;
+      }
+    } catch (IOException e) {
+      // ignore
+    }
+    byteCount += s.length(); // not necessarily exactly correct if unicode
+    return this;
+  }
+
+  @Override
+  public void reset() {
+    sb = null;
+    initOS();
+  }
+
+
+  private void initOS() {
+    if (sb != null) {
+      String s = sb.toString();
+      reset();
+      append(s);
+      return;
+    }
+    try {
+      /**
+       * @j2sNative
+       * 
+       *            this.os = null;
+       */
+      {
+        if (os instanceof FileOutputStream) {
+          os.close();
+          os = new FileOutputStream(fileName);
+        } else {
+          os = null;
+        }
+      }
+      if (os == null)
+        os = new ByteArrayOutputStream();
+      if (bw != null) {
+        bw.close();
+        bw = Rdr.getBufferedWriter(os, null);
+      }
+    } catch (Exception e) {
+      // not perfect here.
+      System.out.println(e.toString());
+    }
+    byteCount = 0;
+  }
+
+  /**
+   * @param b  
+   */
+  @Override
+  public void writeByteAsInt(int b) {
+    if (os == null)
+      initOS();
+    {
+      try {
+        os.write(b);
+      } catch (IOException e) {
+      }
+    }
+    byteCount++;
+  }
+  
+  @Override
+  public void write(byte[] buf, int i, int len) {
+    if (os == null)
+      initOS();
+    try {
+      os.write(buf, i, len);
+    } catch (IOException e) {
+    }
+    byteCount += len;
+  }
+  
+  @Override
+  public void writeShort(short i) {
+    if (isBigEndian()) {
+      writeByteAsInt(i >> 8);
+      writeByteAsInt(i);
+    } else {
+      writeByteAsInt(i);
+      writeByteAsInt(i >> 8);
+    }
+  }
+
+  @Override
+  public void writeLong(long b) {
+    if (isBigEndian()) {
+      writeInt((int) ((b >> 32) & 0xFFFFFFFFl));
+      writeInt((int) (b & 0xFFFFFFFFl));
+    } else {
+      writeByteAsInt((int) (b >> 56));
+      writeByteAsInt((int) (b >> 48));
+      writeByteAsInt((int) (b >> 40));
+      writeByteAsInt((int) (b >> 32));
+      writeByteAsInt((int) (b >> 24));
+      writeByteAsInt((int) (b >> 16));
+      writeByteAsInt((int) (b >> 8));
+      writeByteAsInt((int) b);
+    }
+  }
+
+  public void write(int b) {
+    // required by standard ZipOutputStream -- do not use, as it will break JavaScript methods
+    if (os == null)
+      initOS();
+    try {
+      os.write(b);
+    } catch (IOException e) {
+    }
+    byteCount++;
+  }
+
+  public void write(byte[] b) {
+    // not used in JavaScript due to overloading problem there
+    write(b, 0, b.length);
+  }
+
+  public void cancel() {
+    isCanceled = true;
+    closeChannel();
+  }
+
+  @Override
+  @SuppressWarnings({ "unused" })
+  public String closeChannel() {
+    if (closed)
+      return null;
+    // can't cancel file writers
+    try {
+      if (bw != null) {
+        bw.flush();
+        bw.close();
+      } else if (os != null) {
+        os.flush();
+        os.close();
+      }
+      if (os0 != null && isCanceled) {
+        os0.flush();
+        os0.close();
+      }
+    } catch (Exception e) {
+      // ignore closing issues
+    }
+    if (isCanceled) {
+      closed = true;
+      return null;
+    }
+    if (fileName == null) {
+      if (isBase64) {
+        String s = getBase64();
+        if (os0 != null) {
+          os = os0;
+          append(s);
+        }
+        sb = new SB();
+        sb.append(s);
+        isBase64 = false;
+        return closeChannel();
+      }
+      return (sb == null ? null : sb.toString());
+    }
+    closed = true;
+    J2SObjectInterface jmol = null;
+    Object _function = null;
+    /**
+     * @j2sNative
+     * 
+     *            jmol = self.J2S || Jmol; _function = (typeof this.fileName == "function" ?
+     *            this.fileName : null);
+     * 
+     */
+    {
+      if (!isLocalFile) {
+        String ret = postByteArray(); // unsigned applet could do this
+        if (ret.startsWith("java.net"))
+          byteCount = -1;
+        return ret;
+      }
+    }
+    if (jmol != null) {
+      Object data = (sb == null ? toByteArray() : sb.toString());
+      if (_function == null)
+        jmol._doAjax(fileName, null, data, sb == null);
+      else
+        jmol._apply(_function, data);
+    }
+    return null;
+  }
+
+       public boolean isBase64() {
+               return isBase64;
+       }
+
+       public String getBase64() {
+    return Base64.getBase64(toByteArray()).toString();
+       }
+       
+  public byte[] toByteArray() {
+    return (bytes != null ? bytes : os instanceof ByteArrayOutputStream ? ((ByteArrayOutputStream)os).toByteArray() : null);
+  }
+
+  @Override
+  @Deprecated
+  public void close() {
+    closeChannel();
+  }
+
+  @Override
+  public String toString() {
+    if (bw != null)
+      try {
+        bw.flush();
+      } catch (IOException e) {
+        // TODO
+      }
+    if (sb != null)
+      return closeChannel();
+    return byteCount + " bytes";
+  }
+
+  private String postByteArray() {
+    byte[] bytes = (sb == null ? toByteArray() : sb.toString().getBytes());
+    return bytePoster.postByteArray(fileName, bytes);
+  }
+
+  public final static String[] urlPrefixes = { "http:", "https:", "sftp:", "ftp:",
+  "file:" };
+  // note that SFTP is not supported
+  public final static int URL_LOCAL = 4;
+
+  public static boolean isRemote(String fileName) {
+    if (fileName == null)
+      return false;
+    int itype = urlTypeIndex(fileName);
+    return (itype >= 0 && itype != URL_LOCAL);
+  }
+
+  public static boolean isLocal(String fileName) {
+    if (fileName == null)
+      return false;
+    int itype = urlTypeIndex(fileName);
+    return (itype < 0 || itype == URL_LOCAL);
+  }
+
+  public static int urlTypeIndex(String name) {
+    if (name == null)
+      return -2; // local unsigned applet
+    for (int i = 0; i < urlPrefixes.length; ++i) {
+      if (name.startsWith(urlPrefixes[i])) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  @Override
+  public void writeInt(int i) {
+    if (bigEndian) {
+      writeByteAsInt(i >> 24);
+      writeByteAsInt(i >> 16);
+      writeByteAsInt(i >> 8);
+      writeByteAsInt(i);
+    } else {
+      writeByteAsInt(i);
+      writeByteAsInt(i >> 8);
+      writeByteAsInt(i >> 16);
+      writeByteAsInt(i >> 24);
+    }
+  }
+
+  public void writeFloat(float x) {
+    writeInt(x == 0 ? 0 : Float.floatToIntBits(x));
+  }
+
+}
diff --git a/src/javajs/util/P3.java b/src/javajs/util/P3.java
new file mode 100644 (file)
index 0000000..1e1dfdb
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+
+
+/**
+ * A 3 element point that is represented by single precision floating point
+ * x,y,z coordinates.
+ * 
+ * @version specification 1.1, implementation $Revision: 1.10 $, $Date:
+ *          2006/09/08 20:20:20 $
+ * @author Kenji hiranabe
+ * 
+ * additions by Bob Hanson hansonr@stolaf.edu 9/30/2012
+ * for unique constructor and method names
+ * for the optimization of compiled JavaScript using Java2Script
+ * 
+ */
+public class P3 extends T3 {
+
+  public P3() {
+    // ignore T3
+  }
+  
+  public static P3 newP(T3 t) {
+    P3 p = new P3();
+    p.x = t.x;
+    p.y = t.y;
+    p.z = t.z;
+    return p;
+  }
+
+  private static P3 unlikely;
+  
+  public static P3 getUnlikely() {
+    return (unlikely == null ? unlikely = new3((float) Math.PI, (float) Math.E, (float) (Math.PI * Math.E)) : unlikely);
+  }
+  
+  public static P3 new3(float x, float y, float z) {
+    P3 p = new P3();
+    p.x = x;
+    p.y = y;
+    p.z = z;
+    return p;
+  }
+
+}
diff --git a/src/javajs/util/P3i.java b/src/javajs/util/P3i.java
new file mode 100644 (file)
index 0000000..8f39550
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+
+
+/**
+ * A 3 element point that is represented by signed integer x,y,z coordinates.
+ * 
+ * @since Java 3D 1.2
+ * @version specification 1.2, implementation $Revision: 1.9 $, $Date:
+ *          2006/07/28 17:01:33 $
+ * @author Kenji hiranabe
+ * 
+ * 
+ *         additions by Bob Hanson hansonr@stolaf.edu 9/30/2012 for unique
+ *         constructor and method names for the optimization of compiled
+ *         JavaScript using Java2Script
+ */
+public class P3i extends T3i {
+
+  public static P3i new3(int x, int y, int z) {
+    P3i pt = new P3i();
+    pt.x = x;
+    pt.y = y;
+    pt.z = z;
+    return pt;
+  }
+}
diff --git a/src/javajs/util/P4.java b/src/javajs/util/P4.java
new file mode 100644 (file)
index 0000000..6478602
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+
+
+/**
+ * A 4 element point that is represented by single precision floating point
+ * x,y,z,w coordinates.
+ * 
+ * @version specification 1.1, implementation $Revision: 1.9 $, $Date:
+ *          2006/07/28 17:01:32 $
+ * @author Kenji hiranabe
+ * 
+ * additions by Bob Hanson hansonr@stolaf.edu 9/30/2012
+ * for unique constructor and method names
+ * for the optimization of compiled JavaScript using Java2Script
+ */
+public class P4 extends T4 {
+
+  public P4() {
+    // skip T4() constructor
+  }
+  
+  public static P4 new4(float x, float y, float z, float w) {
+    P4 pt = new P4();
+    pt.set4(x, y, z, w);
+    return pt;
+  }
+
+  public static P4 newPt(P4 value) {
+    P4 pt = new P4();
+    pt.set4(value.x, value.y, value.z, value.w);    
+    return pt;
+  }
+
+  /**
+   * Returns the distance between this point and point p1.
+   * 
+   * @param p1
+   *        the other point
+   * @return the distance between these two points
+   */
+  public final float distance4(P4 p1) {
+    double dx = x - p1.x;
+    double dy = y - p1.y;
+    double dz = z - p1.z;
+    double dw = w - p1.w;
+    return (float) Math.sqrt(dx * dx + dy * dy + dz * dz + dw * dw);
+  }
+
+}
diff --git a/src/javajs/util/PT.java b/src/javajs/util/PT.java
new file mode 100644 (file)
index 0000000..81653cf
--- /dev/null
@@ -0,0 +1,1570 @@
+/* $RCSfile$
+ * $Author: hansonr $
+ * $Date: 2007-04-26 16:57:51 -0500 (Thu, 26 Apr 2007) $
+ * $Revision: 7502 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package javajs.util;
+
+import java.lang.reflect.Array;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javajs.api.JSONEncodable;
+
+/**
+ * a combination of Parsing and Text-related utility classes
+ * 
+ * @author hansonr
+ * 
+ */
+
+public class PT {
+
+  public static int parseInt(String str) {
+    return parseIntNext(str, new int[] {0});
+  }
+
+  public static int parseIntNext(String str, int[] next) {
+    int cch = str.length();
+    if (next[0] < 0 || next[0] >= cch)
+      return Integer.MIN_VALUE;
+    return parseIntChecked(str, cch, next);
+  }
+
+  public static int parseIntChecked(String str, int ichMax, int[] next) {
+    boolean digitSeen = false;
+    int value = 0;
+    int ich = next[0];
+    if (ich < 0)
+      return Integer.MIN_VALUE;
+    int ch;
+    while (ich < ichMax && isWhiteSpace(str, ich))
+      ++ich;
+    boolean negative = false;
+    if (ich < ichMax && str.charAt(ich) == 45) { //"-"
+      negative = true;
+      ++ich;
+    }
+    while (ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
+      value = value * 10 + (ch - 48);
+      digitSeen = true;
+      ++ich;
+    }
+    if (!digitSeen)// || !checkTrailingText(str, ich, ichMax))
+      value = Integer.MIN_VALUE;
+    else if (negative)
+      value = -value;
+    next[0] = ich;
+    return value;
+  }
+
+  public static boolean isWhiteSpace(String str, int ich) {
+    char ch;
+    return (ich >= 0 && ((ch = str.charAt(ich)) == ' ' || ch == '\t' || ch == '\n'));
+  }
+
+  /**
+   * A float parser that is 30% faster than Float.parseFloat(x) and also accepts
+   * x.yD+-n
+   * 
+   * @param str
+   * @param ichMax
+   * @param next
+   *        pointer; incremented
+   * @param isStrict
+   * @return value or Float.NaN
+   */
+  public static float parseFloatChecked(String str, int ichMax, int[] next,
+                                         boolean isStrict) {
+    boolean digitSeen = false;
+    int ich = next[0];
+    if (isStrict && str.indexOf('\n') != str.lastIndexOf('\n'))
+      return Float.NaN;
+    while (ich < ichMax && isWhiteSpace(str, ich))
+      ++ich;
+    boolean negative = false;
+    if (ich < ichMax && str.charAt(ich) == '-') {
+      ++ich;
+      negative = true;
+    }
+    // looks crazy, but if we don't do this, Google Closure Compiler will 
+    // write code that Safari will misinterpret in a VERY nasty way -- 
+    // getting totally confused as to long integers and double values
+    
+    // This is Safari figuring out the values of the numbers on the line (x, y, then z):
+  
+    //  ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
+    //  e=1408749273
+    //  -e =-1408749273
+    //  ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
+    //  e=-1821066134
+    //  e=36.532
+    //  ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
+    //  e=-1133871366
+    //  e=31.576
+    //
+    //  "e" values are just before and after the "value = -value" statement.
+    
+    int ch = 0;
+    float ival = 0f;
+    float ival2 = 0f;
+    while (ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
+      ival = (ival * 10f) + (ch - 48)*1f;
+      ++ich;
+      digitSeen = true;
+    }
+    boolean isDecimal = false;
+    int iscale = 0;
+    int nzero = (ival == 0 ? -1 : 0);
+    if (ch == '.') {
+      isDecimal = true;
+      while (++ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
+        digitSeen = true;
+        if (nzero < 0) {
+          if (ch == 48) { 
+            nzero--;
+            continue;
+          }
+          nzero = -nzero;
+        } 
+        if (iscale  < decimalScale.length) {
+          ival2 = (ival2 * 10f) + (ch - 48)*1f;
+          iscale++;
+        }
+      }
+    }
+    float value;
+    
+    // Safari breaks here intermittently converting integers to floats 
+    
+    if (!digitSeen) {
+      value = Float.NaN;
+    } else if (ival2 > 0) {
+      value = ival2 * decimalScale[iscale - 1];
+      if (nzero > 1) {
+        if (nzero - 2 < decimalScale.length) {
+          value *= decimalScale[nzero - 2];
+        } else {
+          value *= Math.pow(10, 1 - nzero);
+        }
+      } else {
+        value += ival;
+      }
+    } else {
+      value = ival;
+    }
+    boolean isExponent = false;
+    if (ich < ichMax && (ch == 69 || ch == 101 || ch == 68)) { // E e D
+      isExponent = true;
+      if (++ich >= ichMax)
+        return Float.NaN;
+      ch = str.charAt(ich);
+      if ((ch == '+') && (++ich >= ichMax))
+        return Float.NaN;
+      next[0] = ich;
+      int exponent = parseIntChecked(str, ichMax, next);
+      if (exponent == Integer.MIN_VALUE)
+        return Float.NaN;
+      if (exponent > 0 && exponent <= tensScale.length)
+        value *= tensScale[exponent - 1];
+      else if (exponent < 0 && -exponent <= decimalScale.length)
+        value *= decimalScale[-exponent - 1];
+      else if (exponent != 0)
+        value *= Math.pow(10, exponent);
+    } else {
+      next[0] = ich; // the exponent code finds its own ichNextParse
+    }
+    // believe it or not, Safari reports the long-equivalent of the 
+    // float value here, then later the float value, after no operation!
+    if (negative)
+      value = -value;
+    if (value == Float.POSITIVE_INFINITY)
+      value = Float.MAX_VALUE;
+    return (!isStrict || (!isExponent || isDecimal)
+        && checkTrailingText(str, next[0], ichMax) ? value : Float.NaN);
+  }
+
+  public final static float[] tensScale = { 10f, 100f, 1000f, 10000f, 100000f, 1000000f };
+  public final static float[] decimalScale = { 
+  0.1f, 
+  0.01f, 
+  0.001f, 
+  0.0001f, 
+  0.00001f,
+  0.000001f, 
+  0.0000001f, 
+  0.00000001f, 
+  0.000000001f
+  };
+  public static boolean checkTrailingText(String str, int ich, int ichMax) {
+    //number must be pure -- no additional characters other than white space or ;
+    char ch;
+    while (ich < ichMax && (isWhitespace(ch = str.charAt(ich)) || ch == ';'))
+      ++ich;
+    return (ich == ichMax);
+  }
+
+  public static float[] parseFloatArray(String str) {
+    return parseFloatArrayNext(str, new int[1], null, null, null);
+  }
+
+  public static int parseFloatArrayInfested(String[] tokens, float[] data) {
+    int len = data.length;
+    int nTokens = tokens.length;
+    int n = 0;
+    int max = 0;
+    for (int i = 0; i >= 0 && i < len && n < nTokens; i++) {
+      float f;
+      while (Float.isNaN(f = parseFloat(tokens[n++])) 
+          && n < nTokens) {
+      }
+      if (!Float.isNaN(f))
+        data[(max = i)] = f;
+      if (n == nTokens)
+        break;
+    }
+    return max + 1;
+  }
+
+  /**
+   * @param str
+   * @param next
+   * @param f
+   * @param strStart or null
+   * @param strEnd   or null
+   * @return array of float values
+   * 
+   */
+  public static float[] parseFloatArrayNext(String str, int[] next, float[] f,
+                                            String strStart, String strEnd) {
+    int n = 0;
+    int pt = next[0];
+    if (pt >= 0) {
+      if (strStart != null) {
+        int p = str.indexOf(strStart, pt);
+        if (p >= 0)
+          next[0] = p + strStart.length();
+      }
+      str = str.substring(next[0]);
+      pt = (strEnd == null ? -1 : str.indexOf(strEnd));
+      if (pt < 0)
+        pt = str.length();
+      else
+        str = str.substring(0, pt);
+      next[0] += pt + 1;
+      String[] tokens = getTokens(str);
+      if (f == null)
+        f = new float[tokens.length];
+      n = parseFloatArrayInfested(tokens, f);
+    }
+    if (f == null)
+      return new float[0];
+    for (int i = n; i < f.length; i++)
+      f[i] = Float.NaN;
+    return f;
+  }
+
+  public static float parseFloatRange(String str, int ichMax, int[] next) {
+    int cch = str.length();
+    if (ichMax > cch)
+      ichMax = cch;
+    if (next[0] < 0 || next[0] >= ichMax)
+      return Float.NaN;
+    return parseFloatChecked(str, ichMax, next, false);
+  }
+
+  public static float parseFloatNext(String str, int[] next) {
+    int cch = (str == null ? -1 : str.length());
+    return (next[0] < 0 || next[0] >= cch ? Float.NaN : parseFloatChecked(str, cch, next, false));
+  }
+
+  public static float parseFloatStrict(String str) {
+    // checks trailing characters and does not allow "1E35" to be float
+    int cch = str.length();
+    if (cch == 0)
+      return Float.NaN;
+    return parseFloatChecked(str, cch, new int[] {0}, true);
+  }
+
+  public static float parseFloat(String str) {
+    return parseFloatNext(str, new int[] {0});
+  }
+
+  public static int parseIntRadix(String s, int i) throws NumberFormatException {
+    /**
+     * 
+     * JavaScript uses parseIntRadix
+     * 
+     * @j2sNative
+     * 
+     *    return Integer.parseIntRadix(s, i);
+     *    
+     */
+    {
+      return Integer.parseInt(s, i);
+    }
+  }
+
+  public static String[] getTokens(String line) {
+    return getTokensAt(line, 0);
+  }
+
+  public static String parseToken(String str) {
+    return parseTokenNext(str, new int[] {0});
+  }
+
+  public static String parseTrimmed(String str) {
+    return parseTrimmedRange(str, 0, str.length());
+  }
+
+  public static String parseTrimmedAt(String str, int ichStart) {
+    return parseTrimmedRange(str, ichStart, str.length());
+  }
+
+  public static String parseTrimmedRange(String str, int ichStart, int ichMax) {
+    int cch = str.length();
+    if (ichMax < cch)
+      cch = ichMax;
+    if (cch < ichStart)
+      return "";
+    return parseTrimmedChecked(str, ichStart, cch);
+  }
+
+  public static String[] getTokensAt(String line, int ich) {
+    if (line == null)
+      return null;
+    int cchLine = line.length();
+    if (ich < 0 || ich > cchLine)
+      return null;
+    int tokenCount = countTokens(line, ich);
+    String[] tokens = new String[tokenCount];
+    int[] next = new int[1];
+    next[0] = ich;
+    for (int i = 0; i < tokenCount; ++i)
+      tokens[i] = parseTokenChecked(line, cchLine, next);
+    return tokens;
+  }
+
+  public static int countChar(String line, char c) {
+    int n = 0;
+    for (int i = line.lastIndexOf(c) + 1; --i >= 0;)
+      if (line.charAt(i) == c)
+        n++;
+    return n;
+  }
+  
+  public static int countTokens(String line, int ich) {
+    int tokenCount = 0;
+    if (line != null) {
+      int ichMax = line.length();
+      while (true) {
+        while (ich < ichMax && isWhiteSpace(line, ich))
+          ++ich;
+        if (ich == ichMax)
+          break;
+        ++tokenCount;
+        do {
+          ++ich;
+        } while (ich < ichMax && !isWhiteSpace(line, ich));
+      }
+    }
+    return tokenCount;
+  }
+
+  public static String parseTokenNext(String str, int[] next) {
+    int cch = str.length();
+    return (next[0] < 0 || next[0] >= cch ? null : parseTokenChecked(str, cch, next));
+  }
+
+  public static String parseTokenRange(String str, int ichMax, int[] next) {
+    int cch = str.length();
+    if (ichMax > cch)
+      ichMax = cch;
+    return (next[0] < 0 || next[0] >= ichMax ? null : parseTokenChecked(str, ichMax, next));
+  }
+
+  public static String parseTokenChecked(String str, int ichMax, int[] next) {
+    int ich = next[0];
+    while (ich < ichMax && isWhiteSpace(str, ich))
+      ++ich;
+    int ichNonWhite = ich;
+    while (ich < ichMax && !isWhiteSpace(str, ich))
+      ++ich;
+    next[0] = ich;
+    return (ichNonWhite == ich ? null : str.substring(ichNonWhite, ich));
+  }
+
+  public static String parseTrimmedChecked(String str, int ich, int ichMax) {
+    while (ich < ichMax && isWhiteSpace(str, ich))
+      ++ich;
+    int ichLast = ichMax - 1;
+    while (ichLast >= ich && isWhiteSpace(str, ichLast))
+      --ichLast;
+    return (ichLast < ich ? "" : str.substring(ich, ichLast + 1));
+  }
+
+//  public static double dVal(String s) throws NumberFormatException {
+//    /**
+//     * @j2sNative
+//     * 
+//     * if(s==null)
+//     *   throw new NumberFormatException("null");
+//     * var d=parseFloat(s);
+//     * if(isNaN(d))
+//     *  throw new NumberFormatException("Not a Number : "+s);
+//     * return d 
+//     * 
+//     */
+//    {
+//      return Double.valueOf(s).doubleValue();
+//    }
+//  }
+//
+//  public static float fVal(String s) throws NumberFormatException {
+//    /**
+//     * @j2sNative
+//     * 
+//     * return this.dVal(s);
+//     */
+//    {
+//      
+//      return Float.parseFloat(s);
+//    }
+//  }
+
+  public static int parseIntRange(String str, int ichMax, int[] next) {
+    int cch = str.length();
+    if (ichMax > cch)
+      ichMax = cch;
+    return (next[0] < 0 || next[0] >= ichMax ? Integer.MIN_VALUE : parseIntChecked(str, ichMax, next));
+  }
+
+  /**
+   * parses a string array for floats. Returns NaN for nonfloats.
+   * 
+   *  @param tokens  the strings to parse
+   *  @param data    the array to fill
+   */
+  public static void parseFloatArrayData(String[] tokens, float[] data) {
+    parseFloatArrayDataN(tokens, data, data.length);
+  }
+
+  /**
+   * parses a string array for floats. Returns NaN for nonfloats or missing data.
+   * 
+   *  @param tokens  the strings to parse
+   *  @param data    the array to fill
+   *  @param nData   the number of elements
+   */
+  public static void parseFloatArrayDataN(String[] tokens, float[] data, int nData) {
+    for (int i = nData; --i >= 0;)
+      data[i] = (i >= tokens.length ? Float.NaN : parseFloat(tokens[i]));
+  }
+
+  /**
+   * 
+   *  proper splitting, even for Java 1.3 -- if the text ends in the run,
+   *  no new line is appended.
+   * 
+   * @param text
+   * @param run
+   * @return  String array
+   */
+  public static String[] split(String text, String run) {
+    if (text.length() == 0)
+      return new String[0];
+    int n = 1;
+    int i = text.indexOf(run);
+    String[] lines;
+    int runLen = run.length();
+    if (i < 0 || runLen == 0) {
+      lines = new String[1];
+      lines[0] = text;
+      return lines;
+    }
+    int len = text.length() - runLen;
+    for (; i >= 0 && i < len; n++)
+      i = text.indexOf(run, i + runLen);
+    lines = new String[n];
+    i = 0;
+    int ipt = 0;
+    int pt = 0;
+    for (; (ipt = text.indexOf(run, i)) >= 0 && pt + 1 < n;) {
+      lines[pt++] = text.substring(i, ipt);
+      i = ipt + runLen;
+    }
+    if (text.indexOf(run, len) != len)
+      len += runLen;
+    lines[pt] = text.substring(i, len);
+    return lines;
+  }
+
+  public final static float FLOAT_MIN_SAFE = 2E-45f; 
+  // Float.MIN_VALUE (1.45E-45) is not reliable with JavaScript because of the float/double difference there
+  
+  /// general static string-parsing class ///
+
+  // next[0] tracks the pointer within the string so these can all be static.
+  // but the methods parseFloat, parseInt, parseToken, parseTrimmed, and getTokens do not require this.
+
+//  public static String concatTokens(String[] tokens, int iFirst, int iEnd) {
+//    String str = "";
+//    String sep = "";
+//    for (int i = iFirst; i < iEnd; i++) {
+//      if (i < tokens.length) {
+//        str += sep + tokens[i];
+//        sep = " ";
+//      }
+//    }
+//    return str;
+//  }
+  
+  public static String getQuotedStringAt(String line, int ipt0) {
+    int[] next = new int[] { ipt0 };
+    return getQuotedStringNext(line, next);
+  }
+  
+  /**
+   * 
+   * @param line
+   * @param next passes [current pointer]
+   * @return quoted string -- does NOT unescape characters
+   */
+  public static String getQuotedStringNext(String line, int[] next) {
+    int i = next[0];
+    if (i < 0 || (i = line.indexOf("\"", i)) < 0)
+      return "";
+    int pt = i + 1;
+    int len = line.length();
+    while (++i < len && line.charAt(i) != '"')
+      if (line.charAt(i) == '\\')
+        i++;
+    next[0] = i + 1;
+    return line.substring(pt, i);
+  }
+  
+  /**
+   * single- or double-quoted string or up to the first space -- like HTML5
+   * not case-sensitive
+   * 
+   * @param line
+   * @param key
+   * @return attribute
+   */
+  public static String getQuotedOrUnquotedAttribute(String line, String key) {
+    if (line == null || key == null)
+      return null;
+    int pt = line.toLowerCase().indexOf(key.toLowerCase() + "=");
+    if (pt < 0 || (pt = pt + key.length() + 1) >= line.length())
+      return "";
+    char c = line.charAt(pt);
+    switch (c) {
+    case '\'':
+    case '"':
+      pt++;
+      break;
+    default:
+      c = ' ';
+      line += " ";
+    }
+    int pt1 = line.indexOf(c, pt);
+    return (pt1 < 0 ? null : line.substring(pt, pt1));
+  }
+  
+  /**
+   * CSV format -- escaped quote is "" WITHIN "..."
+   *
+   * 
+   * @param line
+   * @param next int[2] filled with [ptrQuote1, ptrAfterQuote2]
+   *            next[1] will be -1 if unmatched quotes are found (continuation on next line)
+   * @return unescaped string or null
+   */
+  public static String getCSVString(String line, int[] next) {
+    int i = next[1];
+    if (i < 0 || (i = line.indexOf("\"", i)) < 0)
+      return null;
+    int pt = next[0] = i;
+    int len = line.length();
+    boolean escaped = false;
+    boolean haveEscape = false;
+    while (++i < len 
+        && (line.charAt(i) != '"' || (escaped = (i + 1 < len && line.charAt(i + 1) == '"'))))
+      if (escaped) {
+        escaped = false;
+        haveEscape = true;
+        i++;
+      }
+    if (i >= len) {
+      next[1] = -1;
+      return null; // unmatched
+    }
+    next[1] = i + 1;
+    String s = line.substring(pt + 1, i);
+    return (haveEscape ? rep(rep(s, "\"\"", "\0"), "\0","\"") : s);
+  }
+  
+  public static boolean isOneOf(String key, String semiList) {
+    if (semiList.length() == 0)
+      return false;
+    if (semiList.charAt(0) != ';')
+      semiList = ";" + semiList + ";";
+    return key.indexOf(";") < 0  && semiList.indexOf(';' + key + ';') >= 0;
+  }
+
+  public static String getQuotedAttribute(String info, String name) {
+    int i = info.indexOf(name + "=");
+    return (i < 0 ? null : getQuotedStringAt(info, i));
+  }
+
+  public static float approx(float f, float n) {
+    return Math.round (f * n) / n;
+  }
+
+  /**
+   * Does a clean ITERATIVE replace of strFrom in str with strTo. 
+   * Thus, rep("Testttt", "tt","t") becomes "Test".
+   * 
+   * @param str
+   * @param strFrom
+   * @param strTo
+   * @return replaced string
+   */
+  public static String rep(String str, String strFrom, String strTo) {
+    if (str == null || strFrom.length() == 0 || str.indexOf(strFrom) < 0)
+      return str;
+    boolean isOnce = (strTo.indexOf(strFrom) >= 0);
+    do {
+      str = str.replace(strFrom, strTo);
+    } while (!isOnce && str.indexOf(strFrom) >= 0);
+    return str;
+  }
+
+  public static String formatF(float value, int width, int precision,
+                              boolean alignLeft, boolean zeroPad) {
+    return formatS(DF.formatDecimal(value, precision), width, 0, alignLeft, zeroPad);
+  }
+
+  /**
+   * 
+   * @param value
+   * @param width
+   * @param precision
+   * @param alignLeft
+   * @param zeroPad
+   * @param allowOverflow IGNORED
+   * @return formatted string
+   */
+  public static String formatD(double value, int width, int precision,
+                              boolean alignLeft, boolean zeroPad, boolean allowOverflow) {
+    return formatS(DF.formatDecimal((float)value, -1 - precision), width, 0, alignLeft, zeroPad);
+  }
+
+  /**
+   * 
+   * @param value       
+   * @param width       number of columns
+   * @param precision   precision > 0 ==> precision = number of characters max from left
+   *                    precision < 0 ==> -1 - precision = number of char. max from right
+   * @param alignLeft
+   * @param zeroPad     generally for numbers turned strings
+   * @return            formatted string
+   */
+  public static String formatS(String value, int width, int precision,
+                              boolean alignLeft, boolean zeroPad) {
+    if (value == null)
+      return "";
+    int len = value.length();
+    if (precision != Integer.MAX_VALUE && precision > 0
+        && precision < len)
+      value = value.substring(0, precision);
+    else if (precision < 0 && len + precision >= 0)
+      value = value.substring(len + precision + 1);
+  
+    int padLength = width - value.length();
+    if (padLength <= 0)
+      return value;
+    boolean isNeg = (zeroPad && !alignLeft && value.charAt(0) == '-');
+    char padChar = (zeroPad ? '0' : ' ');
+    char padChar0 = (isNeg ? '-' : padChar);
+  
+    SB sb = new SB();
+    if (alignLeft)
+      sb.append(value);
+    sb.appendC(padChar0);
+    for (int i = padLength; --i > 0;)
+      // this is correct, not >= 0
+      sb.appendC(padChar);
+    if (!alignLeft)
+      sb.append(isNeg ? padChar + value.substring(1) : value);
+    return sb.toString();
+  }
+
+  /**
+   * Does a clean replace of any of the characters in str with chrTo
+   * If strTo contains strFrom, then only a single pass is done.
+   * Otherwise, multiple passes are made until no more replacements can be made.
+   * 
+   * @param str
+   * @param strFrom
+   * @param chTo
+   * @return  replaced string
+   */
+  public static String replaceWithCharacter(String str, String strFrom,
+                                            char chTo) {
+    if (str == null)
+      return null;
+    for (int i = strFrom.length(); --i >= 0;)
+      str = str.replace(strFrom.charAt(i), chTo);
+    return str;
+  }
+
+  /**
+   * Does a clean replace of any of the characters in str with strTo
+   * If strTo contains strFrom, then only a single pass is done.
+   * Otherwise, multiple passes are made until no more replacements can be made.
+   * 
+   * @param str
+   * @param strFrom
+   * @param strTo
+   * @return  replaced string
+   */
+  public static String replaceAllCharacters(String str, String strFrom,
+                                            String strTo) {
+    for (int i = strFrom.length(); --i >= 0;) {
+      String chFrom = strFrom.substring(i, i + 1);
+      str = rep(str, chFrom, strTo);
+    }
+    return str;
+  }
+
+  public static String trim(String str, String chars) {
+    if (str == null || str.length() == 0)
+      return str;
+    if (chars.length() == 0)
+      return str.trim();
+    int len = str.length();
+    int k = 0;
+    while (k < len && chars.indexOf(str.charAt(k)) >= 0)
+      k++;
+    int m = str.length() - 1;
+    while (m > k && chars.indexOf(str.charAt(m)) >= 0)
+      m--;
+    return str.substring(k, m + 1);
+  }
+
+  public static String trimQuotes(String value) {
+    return (value != null && value.length() > 1 && value.startsWith("\"")
+        && value.endsWith("\"") ? value.substring(1, value.length() - 1)
+        : value);
+  }
+
+  public static boolean isNonStringPrimitive(Object info) {
+    // note that we don't use Double, Float, or Integer here
+    // because in JavaScript those would be false for unwrapped primitives
+    // coming from equivalent of Array.get()
+    // Strings will need their own escaped processing
+    
+    return info instanceof Number || info instanceof Boolean;
+  }
+
+//  private static Object arrayGet(Object info, int i) {
+//    /**
+//     * 
+//     * Note that info will be a primitive in JavaScript
+//     * but a wrapped primitive in Java.
+//     * 
+//     * @j2sNative
+//     * 
+//     *            return info[i];
+//     */
+//    {
+//      return Array.get(info, i);
+//    }
+//  }
+//  
+  @SuppressWarnings("unchecked")
+  public static String toJSON(String infoType, Object info) {
+    if (info == null)
+      return packageJSON(infoType, null);
+    if (isNonStringPrimitive(info))
+      return packageJSON(infoType, info.toString());
+    String s = null;
+    SB sb = null;
+    while (true) {
+      if (info instanceof String) {
+        s = (String) info;
+        /**
+         * @j2sNative
+         * 
+         * if (typeof s == "undefined") s = "null"
+         * 
+         */
+        {}
+        
+        if (s.indexOf("{\"") != 0) {
+          //don't doubly fix JSON strings when retrieving status
+          // what about  \1 \2 \3 etc.?
+          s = esc(s);
+        }
+        break;
+      }
+      if (info instanceof JSONEncodable) {
+        // includes javajs.util.BS, org.jmol.script.SV
+        if ((s = ((JSONEncodable) info).toJSON()) == null)
+          s = "null"; // perhaps a list has a null value (group3List, for example)
+        break;
+      }
+      sb = new SB();
+      if (info instanceof Map) {
+        sb.append("{ ");
+        String sep = "";
+        for (String key : ((Map<String, ?>) info).keySet()) {
+          sb.append(sep).append(
+              packageJSON(key, toJSON(null, ((Map<?, ?>) info).get(key))));
+          sep = ",";
+        }
+        sb.append(" }");
+        break;
+      }
+      if (info instanceof Lst) {
+        sb.append("[ ");
+        int n = ((Lst<?>) info).size();
+        for (int i = 0; i < n; i++) {
+          if (i > 0)
+            sb.appendC(',');
+          sb.append(toJSON(null, ((Lst<?>) info).get(i)));
+        }
+        sb.append(" ]");
+        break;
+      }
+      if (info instanceof M34) {
+        // M4 extends M3
+        int len = (info instanceof M4 ? 4 : 3);
+        float[] x = new float[len];
+        M34 m = (M34) info;
+        sb.appendC('[');
+        for (int i = 0; i < len; i++) {
+          if (i > 0)
+            sb.appendC(',');
+          m.getRow(i, x);
+          sb.append(toJSON(null, x));
+        }
+        sb.appendC(']');
+        break;
+      }
+      s = nonArrayString(info);
+      if (s == null) {
+        sb.append("[");
+        int n = AU.getLength(info);
+        for (int i = 0; i < n; i++) {
+          if (i > 0)
+            sb.appendC(',');
+          sb.append(toJSON(null, Array.get(info, i)));
+        }
+        sb.append("]");
+        break;
+      }
+      info = info.toString();
+    }
+    return packageJSON(infoType, (s == null ? sb.toString() : s));
+  }
+
+  /**
+   * Checks to see if an object is an array (including typed arrays), and if it is, returns null;
+   * otherwise it returns the string equivalent of that object.
+   * 
+   * @param x
+   * @return String or null
+   */
+  public static String nonArrayString(Object x) {
+    /**
+     * @j2sNative
+     * 
+     * return (x.constructor == Array || x.BYTES_PER_ELEMENT ? null : x.toString());
+     * 
+     */
+    {
+      try {
+        Array.getLength(x);
+        return null;
+      } catch (Exception e) {
+        return x.toString();
+      }
+    }
+  }
+
+  public static String byteArrayToJSON(byte[] data) {
+    SB sb = new SB();
+    sb.append("[");
+    int n = data.length;
+    for (int i = 0; i < n; i++) {
+      if (i > 0)
+        sb.appendC(',');
+      sb.appendI(data[i] & 0xFF);
+    }
+    sb.append("]");
+    return sb.toString();
+  }
+  
+  public static String packageJSON(String infoType, String info) {
+    return (infoType == null ? info : "\"" + infoType + "\": " + info);
+  }
+
+  public static String escapeUrl(String url) {
+    url = rep(url, "\n", "");
+    url = rep(url, "%", "%25");
+    url = rep(url, "#", "%23");
+    url = rep(url, "[", "%5B");
+    url = rep(url, "\\", "%5C");
+    url = rep(url, "]", "%5D");
+    url = rep(url, " ", "%20");
+    return url;
+  }
+
+  private final static String escapable = "\\\\\tt\rr\nn\"\""; 
+
+  public static String esc(String str) {
+    if (str == null || str.length() == 0)
+      return "\"\"";
+    boolean haveEscape = false;
+    int i = 0;
+    for (; i < escapable.length(); i += 2)
+      if (str.indexOf(escapable.charAt(i)) >= 0) {
+        haveEscape = true;
+        break;
+      }
+    if (haveEscape)
+      while (i < escapable.length()) {
+        int pt = -1;
+        char ch = escapable.charAt(i++);
+        char ch2 = escapable.charAt(i++);
+        SB sb = new SB();
+        int pt0 = 0;
+        while ((pt = str.indexOf(ch, pt + 1)) >= 0) {
+          sb.append(str.substring(pt0, pt)).appendC('\\').appendC(ch2);
+          pt0 = pt + 1;
+        }
+        sb.append(str.substring(pt0, str.length()));
+        str = sb.toString();
+      }    
+    return "\"" + escUnicode(str) + "\"";
+  }
+
+  public static String escUnicode(String str) {
+    for (int i = str.length(); --i >= 0;)
+      if (str.charAt(i) > 0x7F) {
+        String s = "0000" + Integer.toHexString(str.charAt(i));
+        str = str.substring(0, i) + "\\u" + s.substring(s.length() - 4)
+            + str.substring(i + 1);
+      }
+    return str;
+  }
+
+  /**
+   * ensures that a float turned to string has a decimal point
+   * 
+   * @param f
+   * @return string version of float
+   */
+  public static String escF(float f) {
+    String sf = "" + f;
+    /**
+     * @j2sNative
+     * 
+     * if (sf.indexOf(".") < 0 && sf.indexOf("e") < 0)
+     *   sf += ".0";
+     */
+    {
+    }
+    return sf;
+  }
+  public static String join(String[] s, char c, int i0) {
+    if (s.length < i0)
+      return null;
+    SB sb = new SB();
+    sb.append(s[i0++]);
+    for (int i = i0; i < s.length; i++)
+      sb.appendC(c).append(s[i]);
+    return sb.toString();
+  }
+
+  /**
+   * a LIKE "x"    a is a string and equals x
+   * 
+   * a LIKE "*x"   a is a string and ends with x
+   * 
+   * a LIKE "x*"   a is a string and starts with x
+   * 
+   * a LIKE "*x*"  a is a string and contains x
+   *  
+   * @param a
+   * @param b
+   * @return  a LIKE b
+   */
+  public static boolean isLike(String a, String b) {
+    boolean areEqual = a.equals(b);
+    if (areEqual)
+      return true;
+    boolean isStart = b.startsWith("*");
+    boolean isEnd = b.endsWith("*");
+    return (!isStart && !isEnd) ? areEqual
+        : isStart && isEnd ? b.length() == 1 || a.contains(b.substring(1, b.length() - 1))
+        : isStart ? a.endsWith(b.substring(1))
+        : a.startsWith(b.substring(0, b.length() - 1));
+  }
+
+  public static Object getMapValueNoCase(Map<String, ?> h, String key) {
+    if ("this".equals(key))
+      return h;
+    Object val = h.get(key);
+    if (val == null)
+      for (Entry<String, ?> e : h.entrySet())
+        if (e.getKey().equalsIgnoreCase(key))
+          return e.getValue();
+    return val;
+  }
+
+  public static String clean(String s) {
+    return rep(replaceAllCharacters(s, " \t\n\r", " "), "  ", " ").trim();
+  }
+
+  /**
+   * 
+   * fdup      duplicates p or q formats for formatCheck
+   *           and the format() function.
+   * 
+   * @param f
+   * @param pt
+   * @param n
+   * @return     %3.5q%3.5q%3.5q%3.5q or %3.5p%3.5p%3.5p
+   */
+  public static String fdup(String f, int pt, int n) {
+    char ch;
+    int count = 0;
+    for (int i = pt; --i >= 1; ) {
+      if (isDigit(ch = f.charAt(i)))
+        continue;
+      switch (ch) {
+      case '.':
+        if (count++ != 0)
+          return f;
+        continue;
+      case '-':
+        if (i != 1 && f.charAt(i - 1) != '.')
+          return f;
+        continue;
+      default:
+        return f;
+      }
+    }
+    String s = f.substring(0, pt + 1);
+    SB sb = new SB();
+    for (int i = 0; i < n; i++)
+      sb.append(s);
+    sb.append(f.substring(pt + 1));
+    return sb.toString();
+  }
+
+  /**
+   * generic string formatter  based on formatLabel in Atom
+   * 
+   * 
+   * @param strFormat   .... %width.precisionKEY....
+   * @param key      any string to match
+   * @param strT     replacement string or null
+   * @param floatT   replacement float or Float.NaN
+   * @param doubleT  replacement double or Double.NaN -- for exponential
+   * @param doOne    mimic sprintf    
+   * @return         formatted string
+   */
+  
+  private static String formatString(String strFormat, String key, String strT,
+                                    float floatT, double doubleT, boolean doOne) {
+    if (strFormat == null)
+      return null;
+    if ("".equals(strFormat))
+      return "";
+    int len = key.length();
+    if (strFormat.indexOf("%") < 0 || len == 0 || strFormat.indexOf(key) < 0)
+      return strFormat;
+  
+    String strLabel = "";
+    int ich, ichPercent, ichKey;
+    for (ich = 0; (ichPercent = strFormat.indexOf('%', ich)) >= 0
+        && (ichKey = strFormat.indexOf(key, ichPercent + 1)) >= 0;) {
+      if (ich != ichPercent)
+        strLabel += strFormat.substring(ich, ichPercent);
+      ich = ichPercent + 1;
+      if (ichKey > ichPercent + 6) {
+        strLabel += '%';
+        continue;//%12.10x
+      }
+      try {
+        boolean alignLeft = false;
+        if (strFormat.charAt(ich) == '-') {
+          alignLeft = true;
+          ++ich;
+        }
+        boolean zeroPad = false;
+        if (strFormat.charAt(ich) == '0') {
+          zeroPad = true;
+          ++ich;
+        }
+        char ch;
+        int width = 0;
+        while ((ch = strFormat.charAt(ich)) >= '0' && (ch <= '9')) {
+          width = (10 * width) + (ch - '0');
+          ++ich;
+        }
+        int precision = Integer.MAX_VALUE;
+        boolean isExponential = false;
+        if (strFormat.charAt(ich) == '.') {
+          ++ich;
+          if ((ch = strFormat.charAt(ich)) == '-') {
+            isExponential = (strT == null);
+            ++ich;
+          } 
+          if ((ch = strFormat.charAt(ich)) >= '0' && ch <= '9') {
+            precision = ch - '0';
+            ++ich;
+          }
+          if (isExponential)
+            precision = -precision;
+        }
+        String st = strFormat.substring(ich, ich + len);
+        if (!st.equals(key)) {
+          ich = ichPercent + 1;
+          strLabel += '%';
+          continue;
+        }
+        ich += len;
+        if (!Float.isNaN(floatT)) // 'f'
+          strLabel += formatF(floatT, width, precision, alignLeft,
+              zeroPad);
+        else if (strT != null)  // 'd' 'i' or 's'
+          strLabel += formatS(strT, width, precision, alignLeft,
+              zeroPad);
+        else if (!Double.isNaN(doubleT)) // 'e'
+          strLabel += formatD(doubleT, width, precision - 1, alignLeft,
+              zeroPad, true);
+        if (doOne)
+          break;
+      } catch (IndexOutOfBoundsException ioobe) {
+        ich = ichPercent;
+        break;
+      }
+    }
+    strLabel += strFormat.substring(ich);
+    //if (strLabel.length() == 0)
+      //return null;
+    return strLabel;
+  }
+
+  public static String formatStringS(String strFormat, String key, String strT) {
+    return formatString(strFormat, key, strT, Float.NaN, Double.NaN, false);
+  }
+
+  public static String formatStringF(String strFormat, String key, float floatT) {
+    return formatString(strFormat, key, null, floatT, Double.NaN, false);
+  }
+
+  public static String formatStringI(String strFormat, String key, int intT) {
+    return formatString(strFormat, key, "" + intT, Float.NaN, Double.NaN, false);
+  }
+
+  /**
+   * sprintf emulation uses (almost) c++ standard string formats
+   * 
+   * 's' string 'i' or 'd' integer, 'e' double, 'f' float, 'p' point3f 'q'
+   * quaternion/plane/axisangle with added "i" (equal to the insipid "d" --
+   * digits?)
+   * 
+   * @param strFormat
+   * @param list
+   *        a listing of what sort of data will be found in Object[] values, in
+   *        order: s string, f float, i integer, d double, p point3f, q
+   *        quaternion/point4f, S String[], F float[], I int[], and D double[]
+   * @param values
+   *        Object[] containing above types
+   * @return formatted string
+   */
+  public static String sprintf(String strFormat, String list, Object[] values) {
+    if (values == null)
+      return strFormat;
+    int n = list.length();
+    if (n == values.length)
+      try {
+        for (int o = 0; o < n; o++) {
+          if (values[o] == null)
+            continue;
+          switch (list.charAt(o)) {
+          case 's':
+            strFormat = formatString(strFormat, "s", (String) values[o],
+                Float.NaN, Double.NaN, true);
+            break;
+          case 'f':
+            strFormat = formatString(strFormat, "f", null, ((Float) values[o])
+                .floatValue(), Double.NaN, true);
+            break;
+          case 'i':
+            strFormat = formatString(strFormat, "d", "" + values[o], Float.NaN,
+                Double.NaN, true);
+            strFormat = formatString(strFormat, "i", "" + values[o], Float.NaN,
+                Double.NaN, true);
+            break;
+          case 'd':
+            strFormat = formatString(strFormat, "e", null, Float.NaN,
+                ((Double) values[o]).doubleValue(), true);
+            break;
+          case 'p':
+            T3 pVal = (T3) values[o];
+            strFormat = formatString(strFormat, "p", null, pVal.x, Double.NaN,
+                true);
+            strFormat = formatString(strFormat, "p", null, pVal.y, Double.NaN,
+                true);
+            strFormat = formatString(strFormat, "p", null, pVal.z, Double.NaN,
+                true);
+            break;
+          case 'q':
+            T4 qVal = (T4) values[o];
+            strFormat = formatString(strFormat, "q", null, qVal.x, Double.NaN,
+                true);
+            strFormat = formatString(strFormat, "q", null, qVal.y, Double.NaN,
+                true);
+            strFormat = formatString(strFormat, "q", null, qVal.z, Double.NaN,
+                true);
+            strFormat = formatString(strFormat, "q", null, qVal.w, Double.NaN,
+                true);
+            break;
+          case 'S':
+            String[] sVal = (String[]) values[o];
+            for (int i = 0; i < sVal.length; i++)
+              strFormat = formatString(strFormat, "s", sVal[i], Float.NaN,
+                  Double.NaN, true);
+            break;
+          case 'F':
+            float[] fVal = (float[]) values[o];
+            for (int i = 0; i < fVal.length; i++)
+              strFormat = formatString(strFormat, "f", null, fVal[i],
+                  Double.NaN, true);
+            break;
+          case 'I':
+            int[] iVal = (int[]) values[o];
+            for (int i = 0; i < iVal.length; i++)
+              strFormat = formatString(strFormat, "d", "" + iVal[i], Float.NaN,
+                  Double.NaN, true);
+            for (int i = 0; i < iVal.length; i++)
+              strFormat = formatString(strFormat, "i", "" + iVal[i], Float.NaN,
+                  Double.NaN, true);
+            break;
+          case 'D':
+            double[] dVal = (double[]) values[o];
+            for (int i = 0; i < dVal.length; i++)
+              strFormat = formatString(strFormat, "e", null, Float.NaN,
+                  dVal[i], true);
+          }
+  
+        }
+        return rep(strFormat, "%%", "%");
+      } catch (Exception e) {
+        //
+      }
+    System.out.println("TextFormat.sprintf error " + list + " " + strFormat);
+    return rep(strFormat, "%", "?");
+  }
+
+  /**
+   * 
+   * formatCheck   checks p and q formats and duplicates if necessary
+   *               "%10.5p xxxx" ==> "%10.5p%10.5p%10.5p xxxx" 
+   * 
+   * @param strFormat
+   * @return    f or dupicated format
+   */
+  public static String formatCheck(String strFormat) {
+    if (strFormat == null || strFormat.indexOf('p') < 0 && strFormat.indexOf('q') < 0)
+      return strFormat;
+    strFormat = rep(strFormat, "%%", "\1");
+    strFormat = rep(strFormat, "%p", "%6.2p");
+    strFormat = rep(strFormat, "%q", "%6.2q");
+    String[] format = split(strFormat, "%");
+    SB sb = new SB();
+    sb.append(format[0]);
+    for (int i = 1; i < format.length; i++) {
+      String f = "%" + format[i];
+      int pt;
+      if (f.length() >= 3) {
+        if ((pt = f.indexOf('p')) >= 0)
+          f = fdup(f, pt, 3);
+        if ((pt = f.indexOf('q')) >= 0)
+          f = fdup(f, pt, 4);
+      }
+      sb.append(f);
+    }
+    return sb.toString().replace('\1', '%');
+  }
+
+  public static void leftJustify(SB s, String s1, String s2) {
+    s.append(s2);
+    int n = s1.length() - s2.length();
+    if (n > 0)
+      s.append(s1.substring(0, n));
+  }
+
+  public static void rightJustify(SB s, String s1, String s2) {
+    int n = s1.length() - s2.length();
+    if (n > 0)
+      s.append(s1.substring(0, n));
+    s.append(s2);
+  }
+
+  public static String safeTruncate(float f, int n) {
+    if (f > -0.001 && f < 0.001)
+      f = 0;
+    return (f + "         ").substring(0,n);
+  }
+
+  public static boolean isWild(String s) {
+    return s != null && (s.indexOf("*") >= 0 || s.indexOf("?") >= 0);
+  }
+
+  /**
+   * A general non-regex (for performance) text matcher that utilizes ? and *.
+   * 
+   * ??? means "at most three" characters if at beginning or end; 
+   *   "exactly three" otherwise
+   * \1 in search is a stand-in for actual ?
+   * 
+   * @param search
+   *        the string to search
+   * @param match
+   *        the match string
+   * @param checkStar
+   * @param allowInitialStar
+   * @return true if found
+   */
+  public static boolean isMatch(String search, String match, boolean checkStar,
+                                boolean allowInitialStar) {
+    // search == match --> true
+    if (search.equals(match))
+      return true;
+    int mLen = match.length();
+    // match == ""  --> false
+    if (mLen == 0)
+      return false;
+    boolean isStar0 = (checkStar && allowInitialStar ? match.charAt(0) == '*'
+        : false);
+    // match == "*" --> true
+    if (mLen == 1 && isStar0)
+      return true;
+    boolean isStar1 = (checkStar && match.endsWith("*"));
+    boolean haveQ = (match.indexOf('?') >= 0);
+    // match == "**" --> true
+    // match == "*xxx*" --> search contains "xxx"
+    // match == "*xxx" --> search ends with "xxx"
+    // match == "xxx*" --> search starts with "xxx"
+    if (!haveQ) {
+      if (isStar0)
+        return (isStar1 ? (mLen < 3 || search.indexOf(match.substring(1,
+            mLen - 1)) >= 0) : search.endsWith(match.substring(1)));
+      else if (isStar1)
+        return search.startsWith(match.substring(0, mLen - 1));
+    }
+    int sLen = search.length();
+    // pad match with "?" -- same as *
+    String qqqq = "????";
+    int nq = 4;
+    while (nq < sLen) {
+      qqqq += qqqq;
+      nq += 4;
+    }
+    if (checkStar) {
+      if (isStar0) {
+        match = qqqq + match.substring(1);
+        mLen += nq - 1;
+      }
+      if (isStar1) {
+        match = match.substring(0, mLen - 1) + qqqq;
+        mLen += nq - 1;
+      }
+    }
+    // length of match < length of search --> false 
+    if (mLen < sLen)
+      return false;
+  
+    // -- each ? matches ONE character if not at end
+    // -- extra ? at end ignored
+  
+    // (allowInitialStar == true)
+    // -- extra ? at beginning reduced to match length
+  
+    int ich = 0;
+    while (mLen > sLen) {
+      if (allowInitialStar && match.charAt(ich) == '?') {
+        ++ich;
+      } else if (match.charAt(ich + mLen - 1) != '?') {
+        return false;
+      }
+      --mLen;
+    }
+  
+    // both are effectively same length now.
+    // \1 is stand-in for "?"
+  
+    for (int i = sLen; --i >= 0;) {
+      char chm = match.charAt(ich + i);
+      if (chm == '?')
+        continue;
+      char chs = search.charAt(i);
+      if (chm != chs && (chm != '\1' || chs != '?'))
+        return false;
+    }
+    return true;
+  }
+
+  public static String replaceQuotedStrings(String s, Lst<String> list,
+                                            Lst<String> newList) {
+    int n = list.size();
+    for (int i = 0; i < n; i++) {
+      String name = list.get(i);
+      String newName = newList.get(i);
+      if (!newName.equals(name))
+        s = rep(s, "\"" + name + "\"", "\"" + newName
+            + "\"");
+    }
+    return s;
+  }
+
+  public static String replaceStrings(String s, Lst<String> list,
+                                      Lst<String> newList) {
+    int n = list.size();
+    for (int i = 0; i < n; i++) {
+      String name = list.get(i);
+      String newName = newList.get(i);
+      if (!newName.equals(name))
+        s = rep(s, name, newName);
+    }
+    return s;
+  }
+
+  public static boolean isDigit(char ch) {
+    // just way simpler code than  Character.isDigit(ch);
+    int c = ch;
+    return (48 <= c && c <= 57);
+  }
+
+  public static boolean isUpperCase(char ch) {
+    int c = ch;
+    return (65 <= c && c <= 90);
+  }
+
+  public static boolean isLowerCase(char ch) {
+    int c = ch;
+    return (97 <= c && c <= 122);
+  }
+
+  public static boolean isLetter(char ch) {
+    // just way simpler code than     Character.isLetter(ch);
+    int c = ch;
+    return (65 <= c && c <= 90 || 97 <= c && c <= 122);
+  }
+
+  public static boolean isLetterOrDigit(char ch) {
+    // just way simpler code than     Character.isLetterOrDigit(ch);
+    int c = ch;
+    return (65 <= c && c <= 90 || 97 <= c && c <= 122 || 48 <= c && c <= 57);
+  }
+
+  public static boolean isWhitespace(char ch) {
+    int c = ch;
+    return (c >= 0x1c && c <= 0x20 || c >= 0x9 && c <= 0xd);
+  }
+
+  public static final float FRACTIONAL_PRECISION = 100000f;
+  public static final float CARTESIAN_PRECISION =  10000f;
+
+  public static void fixPtFloats(T3 pt, float f) {
+    //this will equate float and double as long as -256 <= x <= 256
+    pt.x = Math.round(pt.x * f) / f;
+    pt.y = Math.round(pt.y * f) / f;
+    pt.z = Math.round(pt.z * f) / f;
+  }
+  
+  public static double fixDouble(double d, double f) {
+    return Math.round(d * f) / f;
+  }
+
+  /**
+   * parse a float or "float/float"
+   * @param s
+   * @return a/b
+   */
+  public static float parseFloatFraction(String s) {
+      int pt = s.indexOf("/");
+      return (pt < 0 ? parseFloat(s) : parseFloat(s.substring(0, pt))
+          / parseFloat(s.substring(pt + 1)));
+  }
+
+//static {
+//    
+//  double d = 790.8999998888;
+//  float x  = 790.8999998888f;
+//  for (int i = 0; i < 50; i++) {
+//  System.out.println(x + " " + d);
+//  System.out.println(Math.round(x * 100000) / 100000f);
+//  System.out.println(Math.round(d * 100000) / 100000.);
+//  System.out.println(Math.round(x * 10000) / 10000f);
+//  System.out.println(Math.round(d * 10000) / 10000.);
+//  x+=1; 
+//  d+=1;
+//  }
+//  System.out.println(100.123456789f);
+//}
+
+//  static {
+//    long t;
+//    char c = '0';
+//    t = System.currentTimeMillis();
+//    for (int i = 0; i < 10000000; i++) {
+//      boolean b = PT.isUpperCase(c);
+//    }
+//    System.out.println(System.currentTimeMillis() - t);
+//
+//    t = System.currentTimeMillis();
+//    for (int i = 0; i < 10000000; i++) {
+//      boolean b = Character.isUpperCase(c);
+//    }
+//    System.out.println(System.currentTimeMillis() - t);
+//    
+//    t = System.currentTimeMillis();
+//    for (int i = 0; i < 10000000; i++) {
+//      boolean b = PT.isUpperCase(c);
+//    }
+//    System.out.println(System.currentTimeMillis() - t);
+//
+//    System.out.println("PT test");
+//  }
+}
diff --git a/src/javajs/util/Quat.java b/src/javajs/util/Quat.java
new file mode 100644 (file)
index 0000000..d4a7bf9
--- /dev/null
@@ -0,0 +1,818 @@
+/* $RCSfile$
+ * $Author: hansonr $
+ * $Date: 2007-04-05 09:07:28 -0500 (Thu, 05 Apr 2007) $
+ * $Revision: 7326 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2003-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.util;
+
+/*
+ * Standard UNIT quaternion math -- for rotation.
+ * 
+ * All rotations can be represented as two identical quaternions. 
+ * This is because any rotation can be considered from either end of the
+ * rotational axis -- either as a + rotation or a - rotation. This code
+ * is designed to always maintain the quaternion with a rotation in the
+ * [0, PI) range. 
+ * 
+ * This ensures that the reported theta is always positive, and the normal
+ * reported is always associated with a positive theta.  
+ * 
+ * @author Bob Hanson, hansonr@stolaf.edu 6/2008
+ * 
+ */
+
+public class Quat {
+  public float q0, q1, q2, q3;
+  private M3 mat;
+
+  private final static P4 qZero = new P4();
+  private static final double RAD_PER_DEG = Math.PI / 180;
+  
+  public Quat() {
+    q0 = 1;
+  }
+
+  public static Quat newQ(Quat q) {
+    Quat q1 = new Quat();
+    q1.set(q);
+    return q1;
+  }
+
+  public static Quat newVA(T3 v, float theta) {
+    Quat q = new Quat();
+    q.setTA(v, theta);
+    return q;
+  }
+
+  public static Quat newM(M3 mat) {
+    Quat q = new Quat();
+    q.setM(M3.newM3(mat));
+    return q;
+  }
+
+  public static Quat newAA(A4 a) {
+    Quat q = new Quat();
+    q.setAA(a);
+    return q;
+  }
+
+  public static Quat newP4(P4 pt) {
+    Quat q = new Quat();
+    q.setP4(pt);
+    return q;
+  }
+
+  /**
+   * Note that q0 is the last parameter here
+   * 
+   * @param q1
+   * @param q2
+   * @param q3
+   * @param q0
+   * @return {q1 q2 q3 q0}
+   */
+  public static Quat new4(float q1, float q2, float q3, float q0) {
+    Quat q = new Quat();
+    if (q0 < -1) {
+      q.q0 = -1;
+      return q;
+    }
+    if (q0 > 1) {
+      q.q0 = 1;
+      return q;
+    }
+    q.q0 = q0;
+    q.q1 = q1;
+    q.q2 = q2;
+    q.q3 = q3;
+    return q;
+  }
+
+  public void set(Quat q) {
+    q0 = q.q0;
+    q1 = q.q1;
+    q2 = q.q2;
+    q3 = q.q3;
+  }
+
+  /**
+   * {x y z w} --> {q1 q2 q3 q0} and factored
+   * 
+   * @param pt
+   */
+  private void setP4(P4 pt) {
+    float factor = (pt == null ? 0 : pt.distance4(qZero));
+    if (factor == 0) {
+      q0 = 1;
+      return;
+    }
+    q0 = pt.w / factor;
+    q1 = pt.x / factor;
+    q2 = pt.y / factor;
+    q3 = pt.z / factor;
+  }
+
+  /**
+   * q = (cos(theta/2), sin(theta/2) * n)
+   * 
+   * @param pt
+   * @param theta
+   */
+  public void setTA(T3 pt, float theta) {
+    if (pt.x == 0 && pt.y == 0 && pt.z == 0) {
+      q0 = 1;
+      return;
+    }
+    double fact = (Math.sin(theta / 2 * RAD_PER_DEG) / Math.sqrt(pt.x
+        * pt.x + pt.y * pt.y + pt.z * pt.z));
+    q0 = (float) (Math.cos(theta / 2 * RAD_PER_DEG));
+    q1 = (float) (pt.x * fact);
+    q2 = (float) (pt.y * fact);
+    q3 = (float) (pt.z * fact);
+  }
+
+  public void setAA(A4 a) {
+    A4 aa = A4.newAA(a);
+    if (aa.angle == 0)
+      aa.y = 1;
+    setM(new M3().setAA(aa));
+  }
+
+  private void setM(M3 mat) {
+
+    /*
+     * Changed 7/16/2008 to double precision for 11.5.48.
+     * 
+     * <quote>
+     *  
+     * RayTrace Software Package, release 3.0.  May 3, 2006.
+     *
+     * Mathematics Subpackage (VrMath)
+     *
+     * Author: Samuel R. Buss
+     *
+     * Software is "as-is" and carries no warranty.  It may be used without
+     *   restriction, but if you modify it, please change the filenames to
+     *   prevent confusion between different versions.  Please acknowledge
+     *   all use of the software in any publications or products based on it.
+     *
+     * Bug reports: Sam Buss, sbuss@ucsd.edu.
+     * Web page: http://math.ucsd.edu/~sbuss/MathCG
+     
+     // Use Shepperd's algorithm, which is stable, does not lose
+     //    significant precision and uses only one sqrt.
+     //   J. Guidance and Control, 1 (1978) 223-224.
+
+     * </quote>
+     * 
+     * Except, that code has errors.
+     * 
+     * CORRECTIONS (as noted below) of Quaternion.cpp. I have reported the bug.
+     *  
+     * -- Bob Hanson
+     * 
+     *  theory:    
+     *         cos(theta/2)^2 = (cos(theta) + 1)/2
+     *  and      
+     *         trace = (1-x^2)ct + (1-y^2)ct + (1-z^2)ct + 1 = 2cos(theta) + 1
+     *  or
+     *         cos(theta) = (trace - 1)/2 
+     *         
+     *  so in general,       
+     *       
+     *       w = cos(theta/2) 
+     *         = sqrt((cos(theta)+1)/2) 
+     *         = sqrt((trace-1)/4+1/2)
+     *         = sqrt((trace+1)/4)
+     *         = sqrt(trace+1)/2
+     *     
+     *  but there are precision issues, so we allow for other situations.
+     *  note -- trace >= 0.5 when cos(theta) >= -0.25 (-104.48 <= theta <= 104.48).
+     *  this code cleverly matches the precision in all four options.
+     *
+     */
+
+    this.mat = mat;
+    
+    double trace = mat.m00 + mat.m11 + mat.m22;
+    double temp;
+    double w, x, y, z;
+    if (trace >= 0.5) {
+      w = Math.sqrt(1.0 + trace);
+      x = (mat.m21 - mat.m12) / w;
+      y = (mat.m02 - mat.m20) / w;
+      z = (mat.m10 - mat.m01) / w;
+    } else if ((temp = mat.m00 + mat.m00 - trace) >= 0.5) {
+      x = Math.sqrt(1.0 + temp);
+      w = (mat.m21 - mat.m12) / x;
+      y = (mat.m10 + mat.m01) / x;
+      z = (mat.m20 + mat.m02) / x;
+    } else if ((temp = mat.m11 + mat.m11 - trace) >= 0.5 
+        || mat.m11 > mat.m22) {
+      y = Math.sqrt(1.0 + temp);
+      w = (mat.m02 - mat.m20) / y;
+      x = (mat.m10 + mat.m01) / y;
+      z = (mat.m21 + mat.m12) / y;
+    } else {
+      z = Math.sqrt(1.0 + mat.m22 + mat.m22 - trace);
+      w = (mat.m10 - mat.m01) / z;
+      x = (mat.m20 + mat.m02) / z; // was -
+      y = (mat.m21 + mat.m12) / z; // was -
+    }
+
+    q0 = (float) (w * 0.5);
+    q1 = (float) (x * 0.5);
+    q2 = (float) (y * 0.5);
+    q3 = (float) (z * 0.5);
+
+    /*
+     *  Originally from http://www.gamedev.net/community/forums/topic.asp?topic_id=448380
+     *  later algorithm was adapted from Visualizing Quaternions, by Andrew J. Hanson
+     *   (Morgan Kaufmann, 2006), page 446
+     *  
+     *  HOWEVER, checking with AxisAngle4f and Quat4f equivalents, it was found that
+     *  BOTH of these sources produce inverted quaternions. So here we do an inversion.
+     *  
+     *  This correction was made in 11.5.42  6/19/2008  -- Bob Hanson
+     *
+     *  former algorithm used:     
+     * /
+     
+     double tr = mat.m00 + mat.m11 + mat.m22; //Matrix trace 
+     double s;
+     double[] q = new double[4];
+     if (tr > 0) {
+     s = Math.sqrt(tr + 1);
+     q0 = (float) (0.5 * s);
+     s = 0.5 / s; // = 1/q0
+     q1 = (float) ((mat.m21 - mat.m12) * s);
+     q2 = (float) ((mat.m02 - mat.m20) * s);
+     q3 = (float) ((mat.m10 - mat.m01) * s);
+     } else {
+     float[][] m = new float[][] { new float[3], new float[3], new float[3] };
+     mat.getRow(0, m[0]);
+     mat.getRow(1, m[1]);
+     mat.getRow(2, m[2]);
+
+     //Find out the biggest element along the diagonal 
+     float max = Math.max(mat.m11, mat.m00);
+     int i = (mat.m22 > max ? 2 : max == mat.m11 ? 1 : 0);
+     int j = (i + 1) % 3;
+     int k = (j + 1) % 3;
+     s = -Math.sqrt(1 + m[i][i] - m[j][j] - m[k][k]);
+     // 0 = 1 + (1-x^2)ct + x^2 -(1-y^2)ct - y^2 - (1-z^2)ct - z^2
+     // 0 = 1 - ct + (x^2 - y^2 - z^2) - (x^2 - y^2 - z^2)ct
+     // 0 = 1 - ct + 2x^2 - 1 - (2x^2)ct + ct
+     // 0 = 2x^2(1 - ct)
+     // theta = 0 (but then trace = 1 + 1 + 1 = 3)
+     // or x = 0. 
+     q[i] = s * 0.5;
+     if (s != 0)
+     s = 0.5 / s; // = 1/q[i]
+     q[j] = (m[i][j] + m[j][i]) * s;
+     q[k] = (m[i][k] + m[k][i]) * s;
+     q0 = (float) ((m[k][j] - m[j][k]) * s);
+     q1 = (float) q[0]; // x
+     q2 = (float) q[1]; // y
+     q3 = (float) q[2]; // z 
+     }
+
+     */
+  }
+
+  /*
+   * if qref is null, "fix" this quaternion
+   * otherwise, return a quaternion that is CLOSEST to the given quaternion
+   * that is, one that gives a positive dot product
+   * 
+   */
+  public void setRef(Quat qref) {
+    if (qref == null) {
+      mul(getFixFactor());
+      return;
+    }
+    if (dot(qref) >= 0)
+      return;
+    q0 *= -1;
+    q1 *= -1;
+    q2 *= -1;
+    q3 *= -1;
+  }
+
+  /**
+   * returns a quaternion frame based on three points (center, x, and any point in xy plane)
+   * or two vectors (vA, vB).
+   * 
+   * @param center  (null for vA/vB option)
+   * @param x
+   * @param xy
+   * @return quaternion for frame
+   */
+  public static final Quat getQuaternionFrame(P3 center, T3 x,
+                                                    T3 xy) {
+    V3 vA = V3.newV(x);
+    V3 vB = V3.newV(xy);
+    if (center != null) {
+      vA.sub(center);
+      vB.sub(center);
+    }
+    return getQuaternionFrameV(vA, vB, null, false);
+  }
+
+  /**
+   * Create a quaternion based on a frame
+   * @param vA
+   * @param vB
+   * @param vC
+   * @param yBased
+   * @return quaternion
+   */
+  public static final Quat getQuaternionFrameV(V3 vA, V3 vB,
+                                                    V3 vC, boolean yBased) {
+    if (vC == null) {
+      vC = new V3();
+      vC.cross(vA, vB);
+      if (yBased)
+        vA.cross(vB, vC);
+    }
+    V3 vBprime = new V3();
+    vBprime.cross(vC, vA);
+    vA.normalize();
+    vBprime.normalize();
+    vC.normalize();
+    M3 mat = new M3();
+    mat.setColumnV(0, vA);
+    mat.setColumnV(1, vBprime);
+    mat.setColumnV(2, vC);
+
+    /*
+     * 
+     * Verification tests using Quat4f and AngleAxis4f:
+     * 
+     System.out.println("quaternion frame matrix: " + mat);
+     
+     Point3f pt2 = new Point3f();
+     mat.transform(Point3f.new3(1, 0, 0), pt2);
+     System.out.println("vA=" + vA + " M(100)=" + pt2);
+     mat.transform(Point3f.new3(0, 1, 0), pt2);
+     System.out.println("vB'=" + vBprime + " M(010)=" + pt2);
+     mat.transform(Point3f.new3(0, 0, 1), pt2);
+     System.out.println("vC=" + vC + " M(001)=" + pt2);
+     Quat4f q4 = new Quat4f();
+     q4.set(mat);
+     System.out.println("----");
+     System.out.println("Quat4f: {" + q4.w + " " + q4.x + " " + q4.y + " " + q4.z + "}");
+     System.out.println("Quat4f: 2xy + 2wz = m10: " + (2 * q4.x * q4.y + 2 * q4.w * q4.z) + " = " + mat.m10);   
+     
+     */
+
+    Quat q = newM(mat);
+
+     /*
+     System.out.println("Quaternion mat from q \n" + q.getMatrix());
+     System.out.println("Quaternion: " + q.getNormal() + " " + q.getTheta());
+     AxisAngle4f a = new AxisAngle4f();
+     a.set(mat);
+     Vector3f v = Vector3f.new3(a.x, a.y, a.z);
+     v.normalize();
+     System.out.println("angleAxis: " + v + " "+(a.angle/Math.PI * 180));
+     */
+     
+    return q;
+  }
+
+  public M3 getMatrix() {
+    if (mat == null)
+      setMatrix();
+    return mat;
+  }
+
+  private void setMatrix() {
+    mat = new M3();
+    // q0 = w, q1 = x, q2 = y, q3 = z
+    mat.m00 = q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3;
+    mat.m01 = 2 * q1 * q2 - 2 * q0 * q3;
+    mat.m02 = 2 * q1 * q3 + 2 * q0 * q2;
+    mat.m10 = 2 * q1 * q2 + 2 * q0 * q3;
+    mat.m11 = q0 * q0 - q1 * q1 + q2 * q2 - q3 * q3;
+    mat.m12 = 2 * q2 * q3 - 2 * q0 * q1;
+    mat.m20 = 2 * q1 * q3 - 2 * q0 * q2;
+    mat.m21 = 2 * q2 * q3 + 2 * q0 * q1;
+    mat.m22 = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3;
+  }
+
+  public Quat add(float x) {
+    // scalar theta addition (degrees) 
+   return newVA(getNormal(), getTheta() + x);
+  }
+
+  public Quat mul(float x) {
+    // scalar theta multiplication
+    return (x == 1 ? new4(q1, q2, q3, q0) : 
+      newVA(getNormal(), getTheta() * x));
+  }
+
+  public Quat mulQ(Quat p) {
+    return new4(
+        q0 * p.q1 + q1 * p.q0 + q2 * p.q3 - q3 * p.q2, 
+        q0 * p.q2 + q2 * p.q0 + q3 * p.q1 - q1 * p.q3, 
+        q0 * p.q3 + q3 * p.q0 + q1 * p.q2 - q2 * p.q1, 
+        q0 * p.q0 - q1 * p.q1 - q2 * p.q2 - q3 * p.q3);
+  }
+
+  public Quat div(Quat p) {
+    // unit quaternions assumed -- otherwise would scale by 1/p.dot(p)
+    return mulQ(p.inv());
+  }
+
+  public Quat divLeft(Quat p) {
+    // unit quaternions assumed -- otherwise would scale by 1/p.dot(p)
+    return this.inv().mulQ(p);
+  }
+
+  public float dot(Quat q) {
+    return this.q0 * q.q0 + this.q1 * q.q1 + this.q2 * q.q2 + this.q3 * q.q3;
+  }
+
+  public Quat inv() {
+    return new4(-q1, -q2, -q3, q0);
+  }
+
+  public Quat negate() {
+    return new4(-q1, -q2, -q3, -q0);
+  }
+
+  /**
+   * ensures 
+   * 
+   * 1) q0 > 0
+   * or
+   * 2) q0 = 0 and q1 > 0
+   * or
+   * 3) q0 = 0 and q1 = 0 and q2 > 0
+   * or
+   * 4) q0 = 0 and q1 = 0 and q2 = 0 and q3 > 0
+   * 
+   * @return 1 or -1  
+   * 
+   */
+
+  private float getFixFactor() {
+    return (q0 < 0 || 
+        q0 == 0 && (q1 < 0 || q1 == 0 && (q2 < 0 || q2 == 0 && q3 < 0)) ? -1 : 1);
+  }
+  
+  public V3 getVector(int i) {
+    return getVectorScaled(i, 1f);
+  }
+
+  public V3 getVectorScaled(int i, float scale) {
+    if (i == -1) {
+      scale *= getFixFactor();
+      return V3.new3(q1 * scale, q2 * scale, q3 * scale);
+    }
+    if (mat == null)
+      setMatrix();
+    V3 v = new V3();
+    mat.getColumnV(i, v);
+    if (scale != 1f)
+      v.scale(scale);
+    return v;
+  }
+
+  /**
+   * 
+   * @return  vector such that 0 <= angle <= 180
+   */
+  public V3 getNormal() {
+    V3 v = getRawNormal(this);
+    v.scale(getFixFactor());
+    return v;
+  }
+
+  private static V3 getRawNormal(Quat q) {
+    V3 v = V3.new3(q.q1, q.q2, q.q3);
+    if (v.length() == 0)
+      return V3.new3(0, 0, 1);
+    v.normalize();
+    return v;
+  }
+
+  /**
+   * 
+   * @return 0 <= angle <= 180 in degrees
+   */
+  public float getTheta() {
+    return (float) (Math.acos(Math.abs(q0)) * 2 * 180 / Math.PI);
+  }
+
+  public float getThetaRadians() {
+    return (float) (Math.acos(Math.abs(q0)) * 2);
+  }
+
+  /**
+   * 
+   * @param v0
+   * @return    vector option closest to v0
+   * 
+   */
+  public V3 getNormalDirected(V3 v0) {
+    V3 v = getNormal();
+    if (v.x * v0.x + v.y * v0.y + v.z * v0.z < 0) {
+      v.scale(-1);
+    }
+    return v;
+  }
+
+  public V3 get3dProjection(V3 v3d) {
+    v3d.set(q1, q2, q3);
+    return v3d;
+  }
+  
+  /**
+   * 
+   * @param axisAngle
+   * @return   fill in theta of axisAngle such that 
+   */
+  public P4 getThetaDirected(P4 axisAngle) {
+    //fills in .w;
+    float theta = getTheta();
+    V3 v = getNormal();
+    if (axisAngle.x * q1 + axisAngle.y * q2 + axisAngle.z * q3 < 0) {
+      v.scale(-1);
+      theta = -theta;
+    }
+    axisAngle.set4(v.x, v.y, v.z, theta);
+    return axisAngle;
+  }
+
+  /**
+   * 
+   * @param vector  a vector, same as for getNormalDirected
+   * @return   return theta 
+   */
+  public float getThetaDirectedV(V3 vector) {
+    //fills in .w;
+    float theta = getTheta();
+    V3 v = getNormal();
+    if (vector.x * q1 + vector.y * q2 + vector.z * q3 < 0) {
+      v.scale(-1);
+      theta = -theta;
+    }
+    return theta;
+  }
+
+  /**
+   *   Quaternions are saved as {q1, q2, q3, q0} 
+   * 
+   * While this may seem odd, it is so that for any point4 -- 
+   * planes, axisangles, and quaternions -- we can use the 
+   * first three coordinates to determine the relavent axis
+   * the fourth then gives us offset to {0,0,0} (plane), 
+   * rotation angle (axisangle), and cos(theta/2) (quaternion).
+   * @return {x y z w} (unnormalized)
+   */
+  public P4 toPoint4f() {
+    return P4.new4(q1, q2, q3, q0); // x,y,z,w
+  }
+
+  public A4 toAxisAngle4f() {
+    double theta = 2 * Math.acos(Math.abs(q0));
+    double sinTheta2 = Math.sin(theta/2);
+    V3 v = getNormal();
+    if (sinTheta2 < 0) {
+      v.scale(-1);
+      theta = Math.PI - theta;
+    }
+    return A4.newVA(v, (float) theta);
+  }
+
+  public T3 transform2(T3 pt, T3 ptNew) {
+    if (mat == null)
+      setMatrix();
+    mat.rotate2(pt, ptNew);
+    return ptNew;
+  }
+
+  public Quat leftDifference(Quat q2) {
+    //dq = q.leftDifference(qnext);//q.inv().mul(qnext);
+    Quat q2adjusted = (this.dot(q2) < 0 ? q2.negate() : q2);
+    return inv().mulQ(q2adjusted);
+  }
+
+  public Quat rightDifference(Quat q2) {
+    //dq = qnext.rightDifference(q);//qnext.mul(q.inv());
+    Quat q2adjusted = (this.dot(q2) < 0 ? q2.negate() : q2);
+    return mulQ(q2adjusted.inv());
+  }
+
+  /**
+   * 
+   *  Java axisAngle / plane / Point4f format
+   *  all have the format {x y z w}
+   *  so we go with that here as well
+   *   
+   * @return  "{q1 q2 q3 q0}"
+   */
+  @Override
+  public String toString() {
+    return "{" + q1 + " " + q2 + " " + q3 + " " + q0 + "}";
+  }
+
+  /**
+   * 
+   * @param data1
+   * @param data2
+   * @param nMax     > 0 --> limit to this number
+   * @param isRelative
+   * 
+   * @return       pairwise array of data1 / data2 or data1 \ data2
+   */
+  public static Quat[] div(Quat[] data1, Quat[] data2, int nMax, boolean isRelative) {
+    int n;
+    if (data1 == null || data2 == null || (n = Math.min(data1.length, data2.length)) == 0)
+      return null;
+    if (nMax > 0 && n > nMax)
+      n = nMax;
+    Quat[] dqs = new Quat[n];
+    for (int i = 0; i < n; i++) {
+      if (data1[i] == null || data2[i] == null)
+        return null;
+      dqs[i] = (isRelative ? data1[i].divLeft(data2[i]) : data1[i].div(data2[i]));
+    }
+    return dqs;
+  }
+  
+  public static Quat sphereMean(Quat[] data, float[] retStddev, float criterion) {
+    // Samuel R. Buss, Jay P. Fillmore: 
+    // Spherical averages and applications to spherical splines and interpolation. 
+    // ACM Trans. Graph. 20(2): 95-126 (2001)
+      if (data == null || data.length == 0)
+        return new Quat();
+      if (retStddev == null)
+        retStddev = new float[1];
+      if (data.length == 1) {
+        retStddev[0] = 0;
+        return newQ(data[0]);
+      }
+      float diff = Float.MAX_VALUE;
+      float lastStddev = Float.MAX_VALUE;
+      Quat qMean = simpleAverage(data);
+      int maxIter = 100; // typically goes about 5 iterations
+      int iter = 0;
+      while (diff > criterion && lastStddev != 0 && iter < maxIter) {
+        qMean = newMean(data, qMean);
+        retStddev[0] = stdDev(data, qMean);
+        diff = Math.abs(retStddev[0] - lastStddev);
+        lastStddev = retStddev[0];
+        //Logger.info(++iter + " sphereMean " + qMean + " stddev=" + lastStddev + " diff=" + diff);
+      }
+      return qMean;
+  }
+
+  /**
+   * Just a starting point.
+   * get average normal vector
+   * scale normal by average projection of vectors onto it
+   * create quaternion from this 3D projection
+   * 
+   * @param ndata
+   * @return approximate average
+   */
+  private static Quat simpleAverage(Quat[] ndata) {
+    V3 mean = V3.new3(0, 0, 1);
+    // using the directed normal ensures that the mean is 
+    // continually added to and never subtracted from 
+    V3 v = ndata[0].getNormal();
+    mean.add(v);
+    for (int i = ndata.length; --i >= 0;)
+      mean.add(ndata[i].getNormalDirected(mean));
+    mean.sub(v);
+    mean.normalize();
+    float f = 0;
+    // the 3D projection of the quaternion is [sin(theta/2)]*n
+    // so dotted with the normalized mean gets us an approximate average for sin(theta/2)
+    for (int i = ndata.length; --i >= 0;)
+      f += Math.abs(ndata[i].get3dProjection(v).dot(mean)); 
+    if (f != 0)
+      mean.scale(f / ndata.length);
+    // now convert f to the corresponding cosine instead of sine
+    f = (float) Math.sqrt(1 - mean.lengthSquared());
+    if (Float.isNaN(f))
+      f = 0;
+    return newP4(P4.new4(mean.x, mean.y, mean.z, f));
+  }
+
+  private static Quat newMean(Quat[] data, Quat mean) {
+    /* quaternion derivatives nicely take care of producing the necessary 
+     * metric. Since dq gives us the normal with the smallest POSITIVE angle, 
+     * we just scale by that -- using degrees.
+     * No special normalization is required.
+     * 
+     * The key is that the mean has been set up already, and dq.getTheta()
+     * will always return a value between 0 and 180. True, for groupings
+     * where dq swings wildly -- 178, 182, 178, for example -- there will
+     * be problems, but the presumption here is that there is a REASONABLE
+     * set of data. Clearly there are spherical data sets that simply cannot
+     * be assigned a mean. (For example, where the three projected points
+     * are equally distant on the sphere. We just can't worry about those
+     * cases here. Rather, if there is any significance to the data,
+     * there will be clusters of projected points, and the analysis will
+     * be meaningful.
+     * 
+     * Note that the hemisphere problem drops out because dq.getNormal() and
+     * dq.getTheta() will never return (n, 182 degrees) but will 
+     * instead return (-n, 2 degrees). That's just what we want in that case.
+     *
+     *  Note that the projection in this case is to 3D -- a set of vectors
+     *  in space with lengths proportional to theta (not the sin(theta/2) 
+     *  that is associated with a quaternion map).
+     *  
+     *  This is officially an "exponential" or "hyperbolic" projection.
+     *  
+     */
+    V3 sum = new V3();
+    V3 v;
+    Quat q, dq;
+    //System.out.println("newMean mean " + mean);
+    for (int i = data.length; --i >= 0;) {
+      q = data[i];
+      dq = q.div(mean);
+      v = dq.getNormal();
+      v.scale(dq.getTheta());
+      sum.add(v);
+    }
+    sum.scale(1f/data.length);
+    Quat dqMean = newVA(sum, sum.length());
+    //System.out.println("newMean dqMean " + dqMean + " " + dqMean.getNormal() + " " + dqMean.getTheta());
+    return dqMean.mulQ(mean);
+  }
+
+  /**
+   * @param data
+   * @param mean
+   * @return     standard deviation in units of degrees
+   */
+  private static float stdDev(Quat[] data, Quat mean) {
+    // the quaternion dot product gives q0 for dq (i.e. q / mean)
+    // that is, cos(theta/2) for theta between them
+    double sum2 = 0;
+    int n = data.length;
+    for (int i = n; --i >= 0;) {
+      float theta = data[i].div(mean).getTheta(); 
+      sum2 += theta * theta;
+    }
+    return (float) Math.sqrt(sum2 / n);
+  }
+
+  public float[] getEulerZYZ() {
+    // http://www.swarthmore.edu/NatSci/mzucker1/e27/diebel2006attitude.pdf
+    double rA, rB, rG;
+    if (q1 == 0 && q2 == 0) {
+      float theta = getTheta();
+      // pure Z rotation - ambiguous
+      return new float[] { q3 < 0 ? -theta : theta , 0, 0 };
+    }
+    rA = Math.atan2(2 * (q2 * q3 + q0 * q1), 2 * (-q1 * q3 + q0 * q2 ));
+    rB = Math.acos(q3 * q3 - q2 * q2 - q1 * q1 + q0 * q0);
+    rG = Math.atan2( 2 * (q2 * q3 - q0 * q1), 2 * (q0 * q2 + q1 * q3));
+    return new float[]  {(float) (rA / RAD_PER_DEG), (float) (rB / RAD_PER_DEG), (float) (rG / RAD_PER_DEG)};
+  } 
+
+  public float[] getEulerZXZ() {
+    // NOT http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
+    // http://www.swarthmore.edu/NatSci/mzucker1/e27/diebel2006attitude.pdf
+    double rA, rB, rG;
+    if (q1 == 0 && q2 == 0) {
+      float theta = getTheta();
+      // pure Z rotation - ambiguous
+      return new float[] { q3 < 0 ? -theta : theta , 0, 0 };
+    }
+    rA = Math.atan2(2 * (q1 * q3 - q0 * q2), 2 * (q0 * q1 + q2 * q3 ));
+    rB = Math.acos(q3 * q3 - q2 * q2 - q1 * q1 + q0 * q0);
+    rG = Math.atan2( 2 * (q1 * q3 + q0 * q2), 2 * (-q2 * q3 + q0 * q1));
+    return new float[]  {(float) (rA / RAD_PER_DEG), (float) (rB / RAD_PER_DEG), (float) (rG / RAD_PER_DEG)};
+  }
+
+}
diff --git a/src/javajs/util/Rdr.java b/src/javajs/util/Rdr.java
new file mode 100644 (file)
index 0000000..995a629
--- /dev/null
@@ -0,0 +1,614 @@
+/* $RCSfile$
+ * $Author: hansonr $
+ * $Date: 2007-04-05 09:07:28 -0500 (Thu, 05 Apr 2007) $
+ * $Revision: 7326 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2003-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+
+import java.util.Map;
+
+import javajs.api.Interface;
+
+
+import javajs.api.GenericCifDataParser;
+import javajs.api.GenericLineReader;
+import javajs.api.GenericZipTools;
+
+/**
+ * A general helper class for a variety of stream and reader functionality
+ * including:
+ * 
+ *  stream and byte magic-number decoding for PNG, PNGJ, ZIP, and GZIP streams
+ *  
+ *  various stream/reader methods, including UTF-encoded stream reading
+ *  
+ *  reflection-protected access to a CIF parser and ZIP tools
+ *  
+ *   
+ * 
+ * 
+ */
+public class Rdr implements GenericLineReader {
+
+  public static class StreamReader extends BufferedReader {
+
+    private BufferedInputStream stream;
+
+    public StreamReader(BufferedInputStream bis, String charSet) throws UnsupportedEncodingException {
+      super(new InputStreamReader(bis, (charSet == null ? "UTF-8" : charSet)));
+      stream = bis;
+    }
+    
+    public BufferedInputStream getStream() {
+      try {
+        stream.reset();
+      } catch (IOException e) {
+        // ignore
+      }
+      return stream;
+    }
+
+  }
+
+  BufferedReader reader;
+
+  public Rdr(BufferedReader reader) {
+    this.reader = reader;
+  }
+  
+  @Override
+  public String readNextLine() throws Exception {
+    return reader.readLine();
+  }
+
+  public static Map<String, Object> readCifData(GenericCifDataParser parser, BufferedReader br) {
+    return parser.set(null, br, false).getAllCifData();
+  }
+  
+  
+  ///////////
+  
+
+  /**
+   * 
+   * Read a UTF-8 byte array fully, converting it to a String.
+   * Called by Jmol's XMLReaders
+   * 
+   * @param bytes
+   * @return a UTF-8 string
+   */
+  public static String bytesToUTF8String(byte[] bytes) {
+       return streamToUTF8String(new BufferedInputStream(new ByteArrayInputStream(bytes)));
+  }
+
+  /**
+   * 
+   * Read a UTF-8 stream fully, converting it to a String.
+   * Called by Jmol's XMLReaders
+   * 
+   * @param bis
+   * @return a UTF-8 string
+   */
+  public static String streamToUTF8String(BufferedInputStream bis) {
+    String[] data = new String[1];
+    try {
+      readAllAsString(getBufferedReader(bis, "UTF-8"), -1, true, data, 0);
+    } catch (IOException e) {
+    }
+    return data[0];
+  }
+
+  /**
+   * Read an input stream fully, saving a byte array, then
+   * return a buffered reader to those bytes converted to string form.
+   * 
+   * @param bis
+   * @param charSet
+   * @return Reader
+   * @throws IOException
+   */
+  public static BufferedReader getBufferedReader(BufferedInputStream bis, String charSet)
+      throws IOException {
+    // could also just make sure we have a buffered input stream here.
+    if (getUTFEncodingForStream(bis) == Encoding.NONE)
+      return new StreamReader(bis, (charSet == null ? "UTF-8" : charSet));
+    byte[] bytes = getLimitedStreamBytes(bis, -1);
+    bis.close();
+    return getBR(charSet == null ? fixUTF(bytes) : new String(bytes, charSet));
+  }
+
+  /**
+   * This method is specifically for strings that are marked for UTF 8 or 16.
+   * 
+   * @param bytes
+   * @return UTF-decoded bytes
+   */
+       public static String fixUTF(byte[] bytes) {
+               Encoding encoding = getUTFEncoding(bytes);
+               if (encoding != Encoding.NONE)
+                       try {
+                               String s = new String(bytes, encoding.name().replace('_', '-'));
+                               switch (encoding) {
+                               case UTF8:
+                               case UTF_16BE:
+                               case UTF_16LE:
+                                       // extra byte at beginning removed
+                                       s = s.substring(1);
+                                       break;
+                               default:
+                                       break;
+                               }
+                               return s;
+                       } catch (UnsupportedEncodingException e) {
+                               System.out.println(e);
+                       }
+               return new String(bytes);
+       }
+
+  private static Encoding getUTFEncoding(byte[] bytes) {
+    if (bytes.length >= 3 && (bytes[0] & 0xFF) == 0xEF && (bytes[1] & 0xFF) == 0xBB && (bytes[2] & 0xFF) == 0xBF)
+      return Encoding.UTF8;
+    if (bytes.length >= 4 && (bytes[0] & 0xFF) == 0 && (bytes[1] & 0xFF) == 0 
+        && (bytes[2] & 0xFF) == 0xFE && (bytes[3] & 0xFF) == 0xFF)
+      return Encoding.UTF_32BE;
+    if (bytes.length >= 4 && (bytes[0] & 0xFF) == 0xFF && (bytes[1] & 0xFF) == 0xFE 
+        && (bytes[2] & 0xFF) == 0 && (bytes[3] & 0xFF) == 0)
+      return Encoding.UTF_32LE;
+    if (bytes.length >= 2 && (bytes[0] & 0xFF) == 0xFF && (bytes[1] & 0xFF) == 0xFE)
+      return Encoding.UTF_16LE;
+    if (bytes.length >= 2 && (bytes[0] & 0xFF) == 0xFE && (bytes[1] & 0xFF) == 0xFF)
+      return Encoding.UTF_16BE;
+    return Encoding.NONE;
+  
+  }
+  
+  ////////// stream type checking //////////
+  
+
+  private static Encoding getUTFEncodingForStream(BufferedInputStream is) throws IOException {
+//    /**
+//     * @j2sNative
+//     * 
+//     *  is.resetStream();
+//     * 
+//     */
+//    {
+//    }
+    byte[] abMagic = new byte[4];
+    abMagic[3] = 1;
+    try{
+    is.mark(5);
+    } catch (Exception e) {
+      return Encoding.NONE;
+    }
+    is.read(abMagic, 0, 4);
+    is.reset();
+    return getUTFEncoding(abMagic);
+  }
+
+  public static boolean isBase64(SB sb) {
+    return (sb.indexOf(";base64,") == 0);
+  }
+
+  public static boolean isCompoundDocumentS(InputStream is) {
+    return isCompoundDocumentB(getMagic(is, 8));
+  }
+
+  public static boolean isCompoundDocumentB(byte[] bytes) {
+    return (bytes.length >= 8 && (bytes[0] & 0xFF) == 0xD0
+        && (bytes[1] & 0xFF) == 0xCF && (bytes[2] & 0xFF) == 0x11
+        && (bytes[3] & 0xFF) == 0xE0 && (bytes[4] & 0xFF) == 0xA1
+        && (bytes[5] & 0xFF) == 0xB1 && (bytes[6] & 0xFF) == 0x1A 
+        && (bytes[7] & 0xFF) == 0xE1);
+  }
+
+  public static boolean isBZip2S(InputStream is) {
+    return isBZip2B(getMagic(is, 3));
+  }
+
+  public static boolean isGzipS(InputStream is) {
+    return isGzipB(getMagic(is, 2));
+  }
+
+  public static boolean isBZip2B(byte[] bytes) {    
+    return (bytes != null && bytes.length >= 3  // BZh
+        && (bytes[0] & 0xFF) == 0x42 && (bytes[1] & 0xFF) == 0x5A  && (bytes[2] & 0xFF) == 0x68);
+}
+
+  public static boolean isGzipB(byte[] bytes) {    
+      return (bytes != null && bytes.length >= 2 
+          && (bytes[0] & 0xFF) == 0x1F && (bytes[1] & 0xFF) == 0x8B);
+  }
+
+  public static boolean isPickleS(InputStream is) {
+    return isPickleB(getMagic(is, 2));
+  }
+  
+  public static boolean isPickleB(byte[] bytes) {    
+      return (bytes != null && bytes.length >= 2 
+          && (bytes[0] & 0xFF) == 0x7D && (bytes[1] & 0xFF) == 0x71);
+  }
+
+  public static boolean isMessagePackS(InputStream is) {
+    return isMessagePackB(getMagic(is, 2));
+  }
+
+  public static boolean isMessagePackB(byte[] bytes) {
+    // look for 'map' start, but PNG files start with 0x89, which is
+    // the MessagePack start for a 9-member map, so in that case we have
+    // to check that the next byte is not "P" as in <89>PNG
+    int b;
+    
+    return (bytes != null && bytes.length >= 1 && (((b = bytes[0] & 0xFF)) == 0xDE || (b & 0xE0) == 0x80 && bytes[1] != 0x50));
+  }
+
+  public static boolean isPngZipStream(InputStream is) {
+    return isPngZipB(getMagic(is, 55));
+  }
+
+  public static boolean isPngZipB(byte[] bytes) {
+    // \0PNGJ starting at byte 50
+    return (bytes[50] == 0 && bytes[51] == 0x50 && bytes[52] == 0x4E && bytes[53] == 0x47 && bytes[54] == 0x4A);
+  }
+
+  /**
+   * Check for a ZIP input stream - starting with "PK<03><04>"
+   * @param is
+   * @return true if a ZIP stream
+   */
+  public static boolean isZipS(InputStream is) {
+    return isZipB(getMagic(is, 4));
+  }
+
+  public static boolean isZipB(byte[] bytes) {
+    return (bytes.length >= 4 
+        && bytes[0] == 0x50  //PK<03><04> 
+        && bytes[1] == 0x4B
+        && bytes[2] == 0x03 
+        && bytes[3] == 0x04);
+  }
+
+  public static byte[] getMagic(InputStream is, int n) {
+    byte[] abMagic = new byte[n];
+//    /**
+//     * @j2sNative
+//     * 
+//     * is.resetStream();
+//     * 
+//     */
+//    {
+//    }
+    try {
+      is.mark(n + 1);
+      is.read(abMagic, 0, n);
+    } catch (IOException e) {
+    }
+    try {
+      is.reset();
+    } catch (IOException e) {
+    }
+    return abMagic;
+  }
+
+  public static String guessMimeTypeForBytes(byte[] bytes) {
+     // only options here are JPEG, PNG, GIF, and BMP
+    switch (bytes.length < 2 ? -1 : bytes[1]) {
+    case 0:
+      return "image/jpg"; // 0xFF 0x00 ...
+    case 0x49:
+      return "image/gif"; // GIF89a...
+    case 0x4D:
+      return "image/BMP"; // BM...
+    case 0x50:
+      return "image/png";
+    default:
+      return  "image/unknown";
+    }
+  }
+
+
+  ////////// stream/byte methods ///////////
+  
+  public static BufferedInputStream getBIS(byte[] bytes) {
+    return new BufferedInputStream(new ByteArrayInputStream(bytes));
+  }
+
+  public static BufferedReader getBR(String string) {
+    return new BufferedReader(new StringReader(string));
+  }
+
+  
+       public static BufferedInputStream toBIS(Object o) {
+               return (AU.isAB(o) ? getBIS((byte[]) o)
+                               : o instanceof SB ? getBIS(Rdr.getBytesFromSB((SB) o))
+                                               : o instanceof String ? getBIS(((String) o).getBytes()) : null);
+       }
+
+
+  /**
+   * Drill down into a GZIP stack until no more layers.
+   * @param jzt 
+   * 
+   * @param bis
+   * @return non-gzipped buffered input stream.
+   * 
+   * @throws IOException
+   */
+  public static BufferedInputStream getUnzippedInputStream(GenericZipTools jzt, BufferedInputStream bis) throws IOException {
+    while (isGzipS(bis))
+      bis = new BufferedInputStream(jzt.newGZIPInputStream(bis));
+    return bis;
+  }
+
+  public static BufferedInputStream getUnzippedInputStreamBZip2(GenericZipTools jzt,
+                                                                BufferedInputStream bis) throws IOException  {
+    while (isBZip2S(bis))
+      bis = new BufferedInputStream(jzt.newBZip2InputStream(bis));
+    return bis;
+  }
+
+
+  /**
+   * Allow for base64-encoding check.
+   * 
+   * @param sb
+   * @return byte array
+   */
+  public static byte[] getBytesFromSB(SB sb) {
+    return (isBase64(sb) ? Base64.decodeBase64(sb.substring(8)) : sb.toBytes(0, -1));    
+  }
+
+ /**
+   * Read a an entire BufferedInputStream for its bytes, and 
+   * either return them or leave them in the designated output channel.
+   *  
+   * @param bis
+   * @param out a destination output channel, or null
+   * @return byte[] (if out is null) or a message indicating length (if not)
+   * 
+   * @throws IOException
+   */
+  public static Object getStreamAsBytes(BufferedInputStream bis,
+                                         OC out) throws IOException {
+    byte[] buf = new byte[1024];
+    byte[] bytes = (out == null ? new byte[4096] : null);
+    int len = 0;
+    int totalLen = 0;
+    while ((len = bis.read(buf, 0, 1024)) > 0) {
+      totalLen += len;
+      if (out == null) {
+        if (totalLen >= bytes.length)
+          bytes = AU.ensureLengthByte(bytes, totalLen * 2);
+        System.arraycopy(buf, 0, bytes, totalLen - len, len);
+      } else {
+        out.write(buf, 0, len);
+      }
+    }
+    bis.close();
+    if (out == null) {
+      return AU.arrayCopyByte(bytes, totalLen);
+    }
+    return totalLen + " bytes";
+  }
+
+  /**
+   * Read a possibly limited number of bytes (when n > 0) from a stream, 
+   * leaving the stream open.
+   * 
+   * @param is an input stream, not necessarily buffered.
+   * @param n the maximum number of bytes to read, or -1 for all
+   * @return the bytes read
+   * 
+   * @throws IOException
+   */
+  public static byte[] getLimitedStreamBytes(InputStream is, long n)
+      throws IOException {
+
+    //Note: You cannot use InputStream.available() to reliably read
+    //      zip data from the web. 
+
+    int buflen = (n > 0 && n < 1024 ? (int) n : 1024);
+    byte[] buf = new byte[buflen];
+    byte[] bytes = new byte[n < 0 ? 4096 : (int) n];
+    int len = 0;
+    int totalLen = 0;
+    if (n < 0)
+      n = Integer.MAX_VALUE;
+    while (totalLen < n && (len = is.read(buf, 0, buflen)) > 0) {
+      totalLen += len;
+      if (totalLen > bytes.length)
+        bytes = AU.ensureLengthByte(bytes, totalLen * 2);
+      System.arraycopy(buf, 0, bytes, totalLen - len, len);
+      if (n != Integer.MAX_VALUE && totalLen + buflen > bytes.length)
+        buflen = bytes.length - totalLen;
+
+    }
+    if (totalLen == bytes.length)
+      return bytes;
+    buf = new byte[totalLen];
+    System.arraycopy(bytes, 0, buf, 0, totalLen);
+    return buf;
+  }
+
+  /**
+   * This method fills data[i] with string data from a file that may or may not
+   * be binary even though it is being read by a reader. It is meant to be used
+   * simple text-based files only.
+   *  
+   * @param br
+   * @param nBytesMax
+   * @param allowBinary
+   * @param data
+   * @param i
+   * @return true if data[i] holds the data; false if data[i] holds an error message. 
+   */
+  public static boolean readAllAsString(BufferedReader br, int nBytesMax, boolean allowBinary, String[] data, int i) {
+    try {
+      SB sb = SB.newN(8192);
+      String line;
+      if (nBytesMax < 0) {
+        line = br.readLine();
+        if (allowBinary || line != null && line.indexOf('\0') < 0
+            && (line.length() != 4 || line.charAt(0) != 65533
+            || line.indexOf("PNG") != 1)) {
+          sb.append(line).appendC('\n');
+          while ((line = br.readLine()) != null)
+            sb.append(line).appendC('\n');
+        }
+      } else {
+        int n = 0;
+        int len;
+        while (n < nBytesMax && (line = br.readLine()) != null) {
+          if (nBytesMax - n < (len = line.length()) + 1)
+            line = line.substring(0, nBytesMax - n - 1);
+          sb.append(line).appendC('\n');
+          n += len + 1;
+        }
+      }
+      br.close();
+      data[i] = sb.toString();
+      return true;
+    } catch (Exception ioe) {
+      data[i] = ioe.toString();
+      return false;
+    }
+  }
+
+  
+  /////////// PNGJ support /////////////
+  
+  
+  /**
+   * Look at byte 50 for "\0PNGJxxxxxxxxx+yyyyyyyyy" where xxxxxxxxx is a byte
+   * offset to the JMOL data and yyyyyyyyy is the length of the data.
+   * 
+   * @param bis
+   * @return same stream or byte stream
+   */
+
+  /**
+   * Retrieve the two numbers in a PNG iTXt tag indicating the 
+   * file pointer for the start of the ZIP data as well as its length.
+   * 
+   * @param bis
+   * @param pt_count
+   */
+  static void getPngZipPointAndCount(BufferedInputStream bis, int[] pt_count) {
+    bis.mark(75);
+    try {
+      byte[] data = getLimitedStreamBytes(bis, 74);
+      bis.reset();
+      int pt = 0;
+      for (int i = 64, f = 1; --i > 54; f *= 10)
+        pt += (data[i] - '0') * f;
+      int n = 0;
+      for (int i = 74, f = 1; --i > 64; f *= 10)
+        n += (data[i] - '0') * f;
+      pt_count[0] = pt;
+      pt_count[1] = n;
+    } catch (Throwable e) {
+      pt_count[1] = 0;
+    }
+  }
+
+  /**
+   * Either advance a PNGJ stream to its zip file data or pull out the ZIP data
+   * bytes and create a new stream for them from which a ZIP utility can start
+   * extracting files.
+   * 
+   * @param bis
+   * @param asNewStream  
+   * @return new buffered ByteArrayInputStream, possibly with no data if there is an error
+   */
+  public static BufferedInputStream getPngZipStream(BufferedInputStream bis, boolean asNewStream) {
+    if (!isPngZipStream(bis))
+      return bis;
+    byte[] data = new byte[0];
+    bis.mark(75);
+    try {
+      int pt_count[] = new int[2];
+      getPngZipPointAndCount(bis, pt_count);
+      if (pt_count[1] != 0) {
+        int pt = pt_count[0];
+        while (pt > 0)
+          pt -= bis.skip(pt);
+        if (!asNewStream)
+          return bis;
+        data = getLimitedStreamBytes(bis, pt_count[1]);
+      }
+    } catch (Throwable e) {
+    } finally {
+      try {
+        if (asNewStream)
+          bis.close();
+      } catch (Exception e) {
+        // ignore
+      }
+    }
+    return getBIS(data);
+  }
+
+  /** We define a request for zip file extraction by vertical bar:
+   *  zipName|interiorFileName. These may be nested if there is a
+   *  zip file contained in a zip file. 
+   *  
+   * @param fileName
+   * @return filename trimmed of interior fileName
+   * 
+   */
+  public static String getZipRoot(String fileName) {
+    int pt = fileName.indexOf("|");
+    return (pt < 0 ? fileName : fileName.substring(0, pt));
+  }
+
+       public static BufferedWriter getBufferedWriter(OutputStream os, String charSetName) {
+               OutputStreamWriter osw = (OutputStreamWriter) Interface.getInstanceWithParams("java.io.OutputStreamWriter", 
+                               new Class<?>[] { java.io.OutputStream.class, String.class }, 
+                               new Object[] { os, charSetName == null ? "UTF-8" : charSetName }
+               );
+               /**
+                * @j2sNative
+                * return osw.getBufferedWriter();
+                * 
+                */
+               {
+                       return new BufferedWriter(osw);
+               }
+       }
+
+  
+}
+
diff --git a/src/javajs/util/SB.java b/src/javajs/util/SB.java
new file mode 100644 (file)
index 0000000..aed71f3
--- /dev/null
@@ -0,0 +1,354 @@
+
+package javajs.util;
+
+import java.nio.charset.Charset;
+
+/**
+ * Interesting thing here is that JavaScript is 3x faster than Java in handling strings.
+ * 
+ * Java StringBuilder is final, unfortunately. I guess they weren't thinking about Java2Script!
+ * 
+ * The reason we have to do this that several overloaded append methods is WAY too expensive
+ * 
+ */
+
+public class SB {
+  
+  private java.lang.StringBuilder sb;
+  String s; // used by JavaScript only; no Java references
+  
+  //TODO: JS experiment with using array and .push() here
+
+  public SB() {
+    /**
+     * @j2sNative
+     * 
+     *            this.s = "";
+     * 
+     */
+    {
+      sb = new java.lang.StringBuilder();
+    }
+  }
+
+  public static SB newN(int n) {
+    /**
+     * @j2sNative
+     *            return new javajs.util.SB(); 
+     */
+    {
+      // not perfect, because it requires defining sb twice. 
+      // We can do better...
+      SB sb = new SB();
+      sb.sb = new java.lang.StringBuilder(n);
+      return sb;
+    }
+  }
+
+  public static SB newS(String s) {
+    /**
+     * @j2sNative 
+     * 
+     * var sb = new javajs.util.SB();
+     * sb.s = s;
+     * return sb; 
+     * 
+     */
+    {
+    SB sb = new SB();
+    sb.sb = new java.lang.StringBuilder(s);
+    return sb;
+    }
+  }
+
+  public SB append(String s) {
+    /**
+     * @j2sNative
+     * 
+     *            this.s += s
+     * 
+     */
+    {
+      sb.append(s);
+    }
+    return this;
+  }
+  
+  public SB appendC(char c) {
+    /**
+     * @j2sNative
+     * 
+     *            this.s += c;
+     */
+    {
+      sb.append(c);
+    }
+    return this;
+    
+  }
+
+  public SB appendI(int i) {
+    /**
+     * @j2sNative
+     * 
+     *            this.s += i
+     * 
+     */
+    {
+      sb.append(i);
+    }
+    return this;
+  }
+
+  public SB appendB(boolean b) {
+    /**
+     * @j2sNative
+     * 
+     *            this.s += b
+     * 
+     */
+    {
+      sb.append(b);
+    }
+    return this;
+  }
+
+  /**
+   * note that JavaScript could drop off the ".0" in "1.0"
+   * @param f
+   * @return this
+   */
+  public SB appendF(float f) {
+    /**
+     * @j2sNative
+     * 
+     * var sf = "" + f;
+     * if (sf.indexOf(".") < 0 && sf.indexOf("e") < 0)
+     *   sf += ".0" ;
+     *            this.s += sf;
+     * 
+     */
+    {
+      sb.append(f);
+    }
+    return this;
+  }
+
+  public SB appendD(double d) {
+    /**
+     * @j2sNative
+     * 
+     * var sf = "" + d;
+     * if (sf.indexOf(".") < 0 && sf.indexOf("e") < 0)
+     *   sf += ".0" ;
+     *            this.s += sf;
+     * 
+     */
+    {
+      sb.append(d);
+    }
+    return this;
+  }
+
+  public SB appendSB(SB buf) {
+    /**
+     * @j2sNative
+     * 
+     *            this.s += buf.s;
+     * 
+     */
+    {
+      sb.append(buf.sb);
+    }
+    return this;
+  }
+
+  public SB appendO(Object data) {
+    if (data != null) {
+      /**
+       * @j2sNative
+       * 
+       *            this.s += data.toString();
+       * 
+       */
+      {
+        sb.append(data);
+      }
+    }
+    return this;
+  }
+
+  public void appendCB(char[] cb, int off, int len) {
+    /**
+     * @j2sNative
+     * 
+     * this.s += cb.slice(off,off+len).join("");
+     * 
+     */
+    {
+       sb.append(cb, off, len);
+    }
+  }
+
+  @Override
+  public String toString() {
+    /**
+     * @j2sNative
+     * 
+     *            return this.s;
+     * 
+     */
+    {
+      return sb.toString();
+    }
+  }
+
+  public int length() {
+    /**
+     * @j2sNative
+     * 
+     *            return this.s.length;
+     * 
+     */
+    {
+      return sb.length();
+    }
+  }
+
+  public int indexOf(String s) {
+    /**
+     * @j2sNative
+     * 
+     *            return this.s.indexOf(s);
+     * 
+     */
+    {
+      return sb.indexOf(s);
+    }
+  }
+
+  public char charAt(int i) {
+    /**
+     * @j2sNative
+     * 
+     *            return this.s.charAt(i);
+     * 
+     */
+    {
+      return sb.charAt(i);
+    }
+  }
+
+  public int charCodeAt(int i) {
+    /**
+     * @j2sNative
+     * 
+     *            return this.s.charCodeAt(i);
+     * 
+     */
+    {
+      return sb.codePointAt(i);
+    }
+  }
+
+  public void setLength(int n) {
+    /**
+     * @j2sNative
+     * 
+     *            this.s = this.s.substring(0, n);
+     */
+    {
+      sb.setLength(n);
+    }
+  }
+
+  public int lastIndexOf(String s) {
+    /**
+     * @j2sNative
+     * 
+     *            return this.s.lastIndexOf(s);
+     */
+    {
+      return sb.lastIndexOf(s);
+    }
+  }
+
+  public int indexOf2(String s, int i) {
+    /**
+     * @j2sNative
+     * 
+     *            return this.s.indexOf(s, i);
+     */
+    {
+      return sb.indexOf(s, i);
+    }
+  }
+
+  public String substring(int i) {
+    /**
+     * @j2sNative
+     * 
+     *            return this.s.substring(i);
+     */
+    {
+      return sb.substring(i);
+    }
+  }
+
+  public String substring2(int i, int j) {
+    /**
+     * @j2sNative
+     * 
+     *            return this.s.substring(i, j);
+     */
+    {
+      return sb.substring(i, j);
+    }
+  }
+
+  /**
+   * simple byte conversion properly implementing UTF-8. * Used for base64
+   * conversion and allows for offset
+   * 
+   * @param off
+   * @param len
+   *        or -1 for full length (then off must = 0)
+   * @return byte[]
+   */
+  public byte[] toBytes(int off, int len) {
+    if (len == 0)
+      return new byte[0];
+    Charset cs;
+    /**
+     * 
+     * just a string in JavaScript
+     * 
+     * @j2sNative
+     * 
+     *            cs = "UTF-8";
+     * 
+     */
+    {
+      cs = Charset.forName("UTF-8");
+    }
+    return (len > 0 ? substring2(off, off + len) 
+        : off == 0 ? toString()
+        : substring2(off, length() - off)).getBytes(cs);
+  }
+
+       public void replace(int start, int end, String str) {
+               /**
+                * @j2sNative
+                * 
+                * this.s = this.s.substring(0, start) + str + this.s.substring(end);
+                */
+               {
+                       sb.replace(start, end, str);
+               }
+       }
+
+       public void insert(int offset, String str) {
+               replace(offset, offset, str);
+       }
+
+}
diff --git a/src/javajs/util/StringDataReader.java b/src/javajs/util/StringDataReader.java
new file mode 100644 (file)
index 0000000..e78a6f8
--- /dev/null
@@ -0,0 +1,51 @@
+/* $RCSfile$
+ * $Author$
+ * $Date$
+ * $Revision$
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2011  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+package javajs.util;
+
+import java.io.StringReader;
+
+
+
+
+
+public class StringDataReader extends DataReader {
+
+  public StringDataReader() {
+    super();
+  }
+  
+  public StringDataReader(String data) {
+    super(new StringReader(data));
+  }
+
+  @Override
+  public DataReader setData(Object data) {
+    return new StringDataReader((String) data);
+  }
+}
\ No newline at end of file
diff --git a/src/javajs/util/T3.java b/src/javajs/util/T3.java
new file mode 100644 (file)
index 0000000..6cd5108
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+import java.io.Serializable;
+
+import javajs.api.JSONEncodable;
+
+/**
+ * A generic 3 element tuple that is represented by single precision floating
+ * point x,y and z coordinates.
+ * 
+ * @version specification 1.1, implementation $Revision: 1.10 $, $Date:
+ *          2006/09/08 20:20:20 $
+ * @author Kenji hiranabe
+ * 
+ * additions by Bob Hanson hansonr@stolaf.edu 9/30/2012
+ * for unique constructor and method names
+ * for the optimization of compiled JavaScript using Java2Script
+ */
+public abstract class T3 implements JSONEncodable, Serializable {
+
+  public float x, y, z;
+
+  public T3() {
+  }
+
+  /**
+   * Sets the value of this tuple to the specified xyz coordinates.
+   * 
+   * @param x
+   *        the x coordinate
+   * @param y
+   *        the y coordinate
+   * @param z
+   *        the z coordinate
+   */
+  public final void set(float x, float y, float z) {
+    this.x = x;
+    this.y = y;
+    this.z = z;
+  }
+
+  /**
+   * Sets the value of this tuple from the 3 values specified in the array.
+   * 
+   * @param t
+   *        the array of length 3 containing xyz in order
+   */
+  public final void setA(float t[]) {
+    // ArrayIndexOutOfBounds is thrown if t.length < 3
+    x = t[0];
+    y = t[1];
+    z = t[2];
+  }
+
+  /**
+   * Sets the value of this tuple to the value of the Tuple3f argument.
+   * 
+   * @param t1
+   *        the tuple to be copied
+   */
+  public final void setT(T3 t1) {
+    x = t1.x;
+    y = t1.y;
+    z = t1.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the vector sum of tuples t1 and t2.
+   * 
+   * @param t1
+   *        the first tuple
+   * @param t2
+   *        the second tuple
+   */
+  public final void add2(T3 t1, T3 t2) {
+    x = t1.x + t2.x;
+    y = t1.y + t2.y;
+    z = t1.z + t2.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the vector sum of itself and tuple t1.
+   * 
+   * @param t1
+   *        the other tuple
+   */
+  public final void add(T3 t1) {
+    x += t1.x;
+    y += t1.y;
+    z += t1.z;
+  }
+
+  /**
+   * Computes the square of the distance between this point and point p1.
+   * 
+   * @param p1
+   *        the other point
+   * @return the square of distance between these two points as a float
+   */
+  public final float distanceSquared(T3 p1) {
+    double dx = x - p1.x;
+    double dy = y - p1.y;
+    double dz = z - p1.z;
+    return (float) (dx * dx + dy * dy + dz * dz);
+  }
+
+  /**
+   * Returns the distance between this point and point p1.
+   * 
+   * @param p1
+   *        the other point
+   * @return the distance between these two points
+   */
+  public final float distance(T3 p1) {
+    return (float) Math.sqrt(distanceSquared(p1));
+  }
+
+  /**
+   * Sets the value of this tuple to the vector difference of tuple t1 and t2
+   * (this = t1 - t2).
+   * 
+   * @param t1
+   *        the first tuple
+   * @param t2
+   *        the second tuple
+   */
+  public final void sub2(T3 t1, T3 t2) {
+    x = t1.x - t2.x;
+    y = t1.y - t2.y;
+    z = t1.z - t2.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the vector difference of itself and tuple
+   * t1 (this = this - t1).
+   * 
+   * @param t1
+   *        the other tuple
+   */
+  public final void sub(T3 t1) {
+    x -= t1.x;
+    y -= t1.y;
+    z -= t1.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the scalar multiplication of itself.
+   * 
+   * @param s
+   *        the scalar value
+   */
+  public final void scale(float s) {
+    x *= s;
+    y *= s;
+    z *= s;
+  }
+
+  /**
+   * Add {a b c}
+   * 
+   * @param a
+   * @param b
+   * @param c
+   */
+  public final void add3(float a, float b, float c) {
+    x += a;
+    y += b;
+    z += c;
+  }
+
+  
+  /**
+   * {x*p.x, y*p.y, z*p.z)  used for three-way scaling
+   * 
+   * @param p
+   */
+  public final void scaleT(T3 p) {
+    x *= p.x;
+    y *= p.y;
+    z *= p.z;
+  }
+
+  
+  /**
+   * Sets the value of this tuple to the scalar multiplication of tuple t1 and
+   * then adds tuple t2 (this = s*t1 + t2).
+   * 
+   * @param s
+   *        the scalar value
+   * @param t1
+   *        the tuple to be multipled
+   * @param t2
+   *        the tuple to be added
+   */
+  public final void scaleAdd2(float s, T3 t1, T3 t2) {
+    x = s * t1.x + t2.x;
+    y = s * t1.y + t2.y;
+    z = s * t1.z + t2.z;
+  }
+
+  
+  /**
+   * average of two tuples
+   * 
+   * @param a
+   * @param b
+   */
+  public void ave(T3 a, T3 b) {
+    x = (a.x + b.x) / 2f;
+    y = (a.y + b.y) / 2f;
+    z = (a.z + b.z) / 2f; 
+  }
+
+  /**
+   * Vector dot product. Was in Vector3f; more useful here, though.
+   * 
+   * @param v
+   *        the other vector
+   * @return this.dot.v
+   */
+  public final float dot(T3 v) {
+    return x * v.x + y * v.y + z * v.z;
+  }
+
+  /**
+   * Returns the squared length of this vector.
+   * Was in Vector3f; more useful here, though.
+   * 
+   * @return the squared length of this vector
+   */
+  public final float lengthSquared() {
+    return x * x + y * y + z * z;
+  }
+
+  /**
+   * Returns the length of this vector.
+   * Was in Vector3f; more useful here, though.
+   * 
+   * @return the length of this vector
+   */
+  public final float length() {
+    return (float) Math.sqrt(lengthSquared());
+  }
+
+  /**
+   * Normalizes this vector in place.
+   * Was in Vector3f; more useful here, though.
+   */
+  public final void normalize() {
+    double d = length();
+
+    // zero-div may occur.
+    x /= d;
+    y /= d;
+    z /= d;
+  }
+
+  /**
+   * Sets this tuple to be the vector cross product of vectors v1 and v2.
+   * 
+   * @param v1
+   *        the first vector
+   * @param v2
+   *        the second vector
+   */
+  public final void cross(T3 v1, T3 v2) {
+    set(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y
+        - v1.y * v2.x);
+  }
+
+  /**
+   * Returns a hash number based on the data values in this object. Two
+   * different Tuple3f objects with identical data values (ie, returns true for
+   * equals(Tuple3f) ) will return the same hash number. Two vectors with
+   * different data members may return the same hash value, although this is not
+   * likely.
+   */
+  @Override
+  public int hashCode() {
+    long bits = 1L;
+    bits = 31L * bits + floatToIntBits(x);
+    bits = 31L * bits + floatToIntBits(y);
+    bits = 31L * bits + floatToIntBits(z);
+    return (int) (bits ^ (bits >> 32));
+  }
+
+  public static int floatToIntBits(float x) {
+    return (x == 0 ? 0 : Float.floatToIntBits(x));
+  }
+
+  /**
+   * Returns true if all of the data members of Tuple3f t1 are equal to the
+   * corresponding data members in this
+   * 
+   * @param t1
+   *        the vector with which the comparison is made.
+   */
+  @Override
+  public boolean equals(Object t1) {
+    if (!(t1 instanceof T3))
+      return false;
+    T3 t2 = (T3) t1;
+    return (x == t2.x && y == t2.y && z == t2.z);
+  }
+
+  /**
+   * Returns a string that contains the values of this Tuple3f. The form is
+   * (x,y,z).
+   * 
+   * @return the String representation
+   */
+  @Override
+  public String toString() {
+    return "{" + x + ", " + y + ", " + z + "}";
+  }
+  
+  @Override
+  public String toJSON() {
+    return "[" + x + "," + y + "," + z + "]";
+  }
+}
diff --git a/src/javajs/util/T3d.java b/src/javajs/util/T3d.java
new file mode 100644 (file)
index 0000000..08d02d7
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+import java.io.Serializable;
+
+/**
+ * A generic 3 element tuple that is represented by double precision floating
+ * point x,y and z coordinates.
+ * 
+ * @version specification 1.1, implementation $Revision: 1.9 $, $Date:
+ *          2006/07/28 17:01:32 $
+ * @author Kenji hiranabe
+ * 
+ * additions by Bob Hanson hansonr@stolaf.edu 9/30/2012
+ * for unique constructor and method names
+ * for the optimization of compiled JavaScript using Java2Script
+ */
+public abstract class T3d implements Serializable {
+  /**
+   * The x coordinate.
+   */
+  public double x;
+
+  /**
+   * The y coordinate.
+   */
+  public double y;
+
+  /**
+   * The z coordinate.
+   */
+  public double z;
+
+  /**
+   * Constructs and initializes a Tuple3d to (0,0,0).
+   */
+  public T3d() {
+  }
+
+  /**
+   * Sets the value of this tuple to the specified xyz coordinates.
+   * 
+   * @param x
+   *        the x coordinate
+   * @param y
+   *        the y coordinate
+   * @param z
+   *        the z coordinate
+   */
+  public final void set(double x, double y, double z) {
+    this.x = x;
+    this.y = y;
+    this.z = z;
+  }
+
+  /**
+   * Sets the value of this tuple from the 3 values specified in the array.
+   * 
+   * @param t
+   *        the array of length 3 containing xyz in order
+   */
+  public final void setA(double t[]) {
+    // ArrayIndexOutOfBounds is thrown if t.length < 3
+    x = t[0];
+    y = t[1];
+    z = t[2];
+  }
+
+  /**
+   * Sets the value of this tuple to the value of the Tuple3d argument.
+   * 
+   * @param t1
+   *        the tuple to be copied
+   */
+  public final void setT(T3d t1) {
+    x = t1.x;
+    y = t1.y;
+    z = t1.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the vector sum of tuples t1 and t2.
+   * 
+   * @param t1
+   *        the first tuple
+   * @param t2
+   *        the second tuple
+   */
+  public final void add2(T3d t1, T3d t2) {
+    x = t1.x + t2.x;
+    y = t1.y + t2.y;
+    z = t1.z + t2.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the vector sum of itself and tuple t1.
+   * 
+   * @param t1
+   *        the other tuple
+   */
+  public final void add(T3d t1) {
+    x += t1.x;
+    y += t1.y;
+    z += t1.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the vector difference of tuple t1 and t2
+   * (this = t1 - t2).
+   * 
+   * @param t1
+   *        the first tuple
+   * @param t2
+   *        the second tuple
+   */
+  public final void sub2(T3d t1, T3d t2) {
+    x = t1.x - t2.x;
+    y = t1.y - t2.y;
+    z = t1.z - t2.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the vector difference of itself and tuple
+   * t1 (this = this - t1).
+   * 
+   * @param t1
+   *        the other tuple
+   */
+  public final void sub(T3d t1) {
+    x -= t1.x;
+    y -= t1.y;
+    z -= t1.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the scalar multiplication of itself.
+   * 
+   * @param s
+   *        the scalar value
+   */
+  public final void scale(double s) {
+    x *= s;
+    y *= s;
+    z *= s;
+  }
+
+  /**
+   * Sets the value of this tuple to the scalar multiplication of tuple t1 and
+   * then adds tuple t2 (this = s*t1 + t2).
+   * 
+   * @param s
+   *        the scalar value
+   * @param t1
+   *        the tuple to be multipled
+   * @param t2
+   *        the tuple to be added
+   */
+  public final void scaleAdd(double s, T3d t1, T3d t2) {
+    x = s * t1.x + t2.x;
+    y = s * t1.y + t2.y;
+    z = s * t1.z + t2.z;
+  }
+
+  /**
+   * Returns a hash number based on the data values in this object. Two
+   * different Tuple3d objects with identical data values (ie, returns true for
+   * equals(Tuple3d) ) will return the same hash number. Two vectors with
+   * different data members may return the same hash value, although this is not
+   * likely.
+   */
+  @Override
+  public int hashCode() {
+    long xbits = doubleToLongBits0(x);
+    long ybits = doubleToLongBits0(y);
+    long zbits = doubleToLongBits0(z);
+    return (int) (xbits ^ (xbits >> 32) ^ ybits ^ (ybits >> 32) ^ zbits ^ (zbits >> 32));
+  }
+
+  static long doubleToLongBits0(double d) {
+    // Check for +0 or -0
+    return (d == 0 ? 0 : Double.doubleToLongBits(d));
+  }
+
+  /**
+   * Returns true if all of the data members of Tuple3d t1 are equal to the
+   * corresponding data members in this
+   * 
+   * @param t1
+   *        the vector with which the comparison is made.
+   */
+  @Override
+  public boolean equals(Object t1) {
+    if (!(t1 instanceof T3d))
+      return false;
+    T3d t2 = (T3d) t1;
+    return (this.x == t2.x && this.y == t2.y && this.z == t2.z);
+  }
+
+  /**
+   * Returns a string that contains the values of this Tuple3d. The form is
+   * (x,y,z).
+   * 
+   * @return the String representation
+   */
+  @Override
+  public String toString() {
+    return "{" + x + ", " + y + ", " + z + "}";
+  }
+
+}
diff --git a/src/javajs/util/T3i.java b/src/javajs/util/T3i.java
new file mode 100644 (file)
index 0000000..7fbd3bb
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+import java.io.Serializable;
+
+/**
+ * A 3-element tuple represented by signed integer x,y,z coordinates.
+ * 
+ * @since Java 3D 1.2
+ * @version specification 1.2, implementation $Revision: 1.9 $, $Date:
+ *          2006/07/28 17:01:32 $
+ * @author Kenji hiranabe
+ * 
+ *         additions by Bob Hanson hansonr@stolaf.edu 9/30/2012 for unique
+ *         constructor and method names for the optimization of compiled
+ *         JavaScript using Java2Script
+ */
+public abstract class T3i implements Serializable {
+
+  /**
+   * The x coordinate.
+   */
+  public int x;
+
+  /**
+   * The y coordinate.
+   */
+  public int y;
+
+  /**
+   * The z coordinate.
+   */
+  public int z;
+
+  /**
+   * Constructs and initializes a Tuple3i to (0,0,0).
+   */
+  public T3i() {
+  }
+
+  /**
+   * Sets the value of this tuple to to the specified x, y, and z coordinates.
+   * 
+   * @param x
+   *        the x coordinate.
+   * @param y
+   *        the y coordinate.
+   * @param z
+   *        the z coordinate.
+   */
+  public final void set(int x, int y, int z) {
+    this.x = x;
+    this.y = y;
+    this.z = z;
+  }
+
+  /**
+   * Sets the value of this tuple to the value of tuple t1.
+   * 
+   * @param t1
+   *        the tuple to be copied.
+   */
+  public final void setT(T3i t1) {
+    x = t1.x;
+    y = t1.y;
+    z = t1.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the sum of itself and t1.
+   * 
+   * @param t
+   *        is the other tuple
+   */
+  public final void add(T3i t) {
+    x += t.x;
+    y += t.y;
+    z += t.z;
+  }
+
+  /**
+   * Sets the value of this tuple to the scalar multiplication of tuple t1 plus
+   * tuple t2 (this = s*t1 + t2).
+   * 
+   * @param s
+   *        the scalar value
+   * @param t1
+   *        the tuple to be multipled
+   * @param t2
+   *        the tuple to be added
+   */
+  public final void scaleAdd(int s, T3i t1, T3i t2) {
+    x = s * t1.x + t2.x;
+    y = s * t1.y + t2.y;
+    z = s * t1.z + t2.z;
+  }
+
+  /**
+   * Returns a hash number based on the data values in this object. Two
+   * different Tuple3i objects with identical data values (ie, returns true for
+   * equals(Tuple3i) ) will return the same hash number. Two vectors with
+   * different data members may return the same hash value, although this is not
+   * likely.
+   */
+  @Override
+  public int hashCode() {
+    return x ^ y ^ z;
+  }
+
+  /**
+   * Returns true if the Object o is of type Tuple3i and all of the data members
+   * of t are equal to the corresponding data members in this Tuple3i.
+   * 
+   * @param o
+   *        the object with which the comparison is made.
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof T3i))
+      return false;
+    T3i t = (T3i) o;
+    return (this.x == t.x && this.y == t.y && this.z == t.z);
+  }
+
+  /**
+   * Returns a string that contains the values of this Tuple3i. The form is
+   * (x,y,z).
+   * 
+   * @return the String representation
+   */
+  @Override
+  public String toString() {
+    return "(" + x + ", " + y + ", " + z + ")";
+  }
+
+}
diff --git a/src/javajs/util/T4.java b/src/javajs/util/T4.java
new file mode 100644 (file)
index 0000000..21b2741
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+/**
+ * A generic 4 element tuple that is represented by single precision floating
+ * point x,y,z and w coordinates.
+ * 
+ * @version specification 1.1, implementation $Revision: 1.9 $, $Date:
+ *          2006/07/28 17:01:32 $
+ * @author Kenji hiranabe
+ * 
+ * additions by Bob Hanson hansonr@stolaf.edu 9/30/2012
+ * for unique constructor and method names
+ * for the optimization of compiled JavaScript using Java2Script
+ */
+public abstract class T4 extends T3 {
+
+  /**
+   * The w coordinate.
+   */
+  public float w;
+
+  /**
+   * Constructs and initializes a Tuple4f to (0,0,0,0).
+   * 
+   */
+  public T4() {
+  }
+
+  /**
+   * Sets the value of this tuple to the specified xyzw coordinates.
+   * 
+   * @param x
+   *        the x coordinate
+   * @param y
+   *        the y coordinate
+   * @param z
+   *        the z coordinate
+   * @param w
+   *        the w coordinate
+   */
+  public final void set4(float x, float y, float z, float w) {
+    this.x = x;
+    this.y = y;
+    this.z = z;
+    this.w = w;
+  }
+
+  /**
+   * Sets the value of this tuple to the scalar multiplication of itself.
+   * 
+   * @param s
+   *        the scalar value
+   */
+  public final void scale4(float s) {
+    scale(s);
+    w *= s;
+  }
+
+  /**
+   * Returns a hash number based on the data values in this object. Two
+   * different Tuple4f objects with identical data values (ie, returns true for
+   * equals(Tuple4f) ) will return the same hash number. Two vectors with
+   * different data members may return the same hash value, although this is not
+   * likely.
+   */
+  @Override
+  public int hashCode() {
+    return T3.floatToIntBits(x) ^ T3.floatToIntBits(y)
+        ^ T3.floatToIntBits(z) ^ T3.floatToIntBits(w);
+  }
+
+  /**
+   * Returns true if all of the data members of Object are equal to the
+   * corresponding data members in this
+   * 
+   * @param o
+   *        the vector with which the comparison is made.
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof T4))
+      return false;
+    T4 t = (T4) o;
+    return (this.x == t.x && this.y == t.y && this.z == t.z && this.w == t.w);
+  }
+
+  /**
+   * Returns a string that contains the values of this Tuple4f. The form is
+   * (x,y,z,w).
+   * 
+   * @return the String representation
+   */
+  @Override
+  public String toString() {
+    return "(" + x + ", " + y + ", " + z + ", " + w + ")";
+  }
+
+  @Override
+  public String toJSON() {
+    return "[" + x + ", " + y + ", " + z + ", " + w + "]";
+  }
+
+}
diff --git a/src/javajs/util/V3.java b/src/javajs/util/V3.java
new file mode 100644 (file)
index 0000000..51325d2
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ Copyright (C) 1997,1998,1999
+ Kenji Hiranabe, Eiwa System Management, Inc.
+
+ This program is free software.
+ Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+ conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+ Permission to use, copy, modify, distribute and sell this software
+ and its documentation for any purpose is hereby granted without fee,
+ provided that the above copyright notice appear in all copies and
+ that both that copyright notice and this permission notice appear
+ in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+ makes no representations about the suitability of this software for any
+ purpose.  It is provided "AS IS" with NO WARRANTY.
+ */
+package javajs.util;
+
+
+/**
+ * A 3-element vector that is represented by single precision floating point
+ * x,y,z coordinates. If this value represents a normal, then it should be
+ * normalized.
+ * 
+ * @version specification 1.1, implementation $Revision: 1.10 $, $Date:
+ *          2006/10/03 19:52:30 $
+ * @author Kenji hiranabe
+ * 
+ * additions by Bob Hanson hansonr@stolaf.edu 9/30/2012
+ * for unique constructor and method names
+ * for the optimization of compiled JavaScript using Java2Script
+ */
+public class V3 extends T3 {
+
+  public V3() {
+  }
+  
+  public static V3 newV(T3 t) {
+    return V3.new3(t.x, t.y, t.z);
+  }
+
+  public static V3 newVsub(T3 t1, T3 t2) {
+    return V3.new3(t1.x - t2.x, t1.y - t2.y,t1.z - t2.z);
+  }
+
+  public static V3 new3(float x, float y, float z) {
+    V3 v = new V3();
+    v.x = x;
+    v.y = y;
+    v.z = z;
+    return v;
+  }
+
+  /**
+   * Returns the angle in radians between this vector and the vector parameter;
+   * the return value is constrained to the range [0,PI].
+   * 
+   * @param v1
+   *        the other vector
+   * @return the angle in radians in the range [0,PI]
+   */
+  public final float angle(V3 v1) {
+    // return (double)Math.acos(dot(v1)/v1.length()/v.length());
+    // Numerically, near 0 and PI are very bad condition for acos.
+    // In 3-space, |atan2(sin,cos)| is much stable.
+
+    double xx = y * v1.z - z * v1.y;
+    double yy = z * v1.x - x * v1.z;
+    double zz = x * v1.y - y * v1.x;
+    double cross = Math.sqrt(xx * xx + yy * yy + zz * zz);
+
+    return (float) Math.abs(Math.atan2(cross, dot(v1)));
+  }
+}
diff --git a/src/javajs/util/V3d.java b/src/javajs/util/V3d.java
new file mode 100644 (file)
index 0000000..9060575
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+   Copyright (C) 1997,1998,1999
+   Kenji Hiranabe, Eiwa System Management, Inc.
+
+   This program is free software.
+   Implemented by Kenji Hiranabe(hiranabe@esm.co.jp),
+   conforming to the Java(TM) 3D API specification by Sun Microsystems.
+
+   Permission to use, copy, modify, distribute and sell this software
+   and its documentation for any purpose is hereby granted without fee,
+   provided that the above copyright notice appear in all copies and
+   that both that copyright notice and this permission notice appear
+   in supporting documentation. Kenji Hiranabe and Eiwa System Management,Inc.
+   makes no representations about the suitability of this software for any
+   purpose.  It is provided "AS IS" with NO WARRANTY.
+*/
+package javajs.util;
+
+
+
+
+/**
+ * A 3 element vector that is represented by double precision floating point
+ * x,y,z coordinates. If this value represents a normal, then it should be
+ * normalized.
+ * 
+ * @version specification 1.1, implementation $Revision: 1.9 $, $Date:
+ *          2006/07/28 17:01:32 $
+ * @author Kenji hiranabe
+ * 
+ * additions by Bob Hanson hansonr@stolaf.edu 9/30/2012
+ * for unique constructor and method names
+ * for the optimization of compiled JavaScript using Java2Script
+ */
+public class V3d extends T3d {
+
+  /**
+   * Sets this vector to be the vector cross product of vectors v1 and v2.
+   * 
+   * @param v1
+   *        the first vector
+   * @param v2
+   *        the second vector
+   */
+  public final void cross(V3d v1, V3d v2) {
+    // store on stack once for aliasing-safty
+    // i.e. safe when a.cross(a, b)
+    set(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y
+        - v1.y * v2.x);
+  }
+
+  /**
+   * Normalizes this vector in place.
+   */
+  public final void normalize() {
+    double d = length();
+
+    // zero-div may occur.
+    x /= d;
+    y /= d;
+    z /= d;
+  }
+
+  /**
+   * Computes the dot product of the this vector and vector v.
+   * 
+   * @param v
+   *        the other vector
+   * @return this.dot.v
+   */
+  public final double dot(V3d v) {
+    return x * v.x + y * v.y + z * v.z;
+  }
+
+  /**
+   * Returns the squared length of this vector.
+   * 
+   * @return the squared length of this vector
+   */
+  public final double lengthSquared() {
+    return x * x + y * y + z * z;
+  }
+
+  /**
+   * Returns the length of this vector.
+   * 
+   * @return the length of this vector
+   */
+  public final double length() {
+    return Math.sqrt(lengthSquared());
+  }
+
+}
diff --git a/src/javajs/util/XmlUtil.java b/src/javajs/util/XmlUtil.java
new file mode 100644 (file)
index 0000000..3e30e4b
--- /dev/null
@@ -0,0 +1,171 @@
+/* $RCSfile$
+ * $Author$
+ * $Date$
+ * $Revision$
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2006  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+package javajs.util;
+
+/**
+ * A very simplistic XML generator
+ */
+
+public class XmlUtil {
+
+  public XmlUtil() {
+    // Jmol's PropertyManager and JvxlCoder classes use reflection 
+  }
+  public static void openDocument(SB data) {
+    data.append("<?xml version=\"1.0\"?>\n");
+  }
+
+  public static void openTag(SB sb, String name) {
+    sb.append("<").append(name).append(">\n");
+  }
+
+  public static void openTagAttr(SB sb, String name, Object[] attributes) {
+    appendTagAll(sb, name, attributes, null, false, false);
+    sb.append("\n");
+  }
+
+  public static void closeTag(SB sb, String name) {
+    sb.append("</").append(name).append(">\n");
+  }
+
+  public static void appendTagAll(SB sb, String name,
+                               Object[] attributes, Object data,
+                               boolean isCdata, boolean doClose) {
+    String closer = ">";
+    if (name.endsWith("/")){
+      name = name.substring(0, name.length() - 1);
+      if (data == null) {
+        closer = "/>\n";
+        doClose = false;
+      }
+    }
+    sb.append("<").append(name);
+    if (attributes != null)
+      for (int i = 0; i < attributes.length; i++) {
+        Object o = attributes[i];
+        if (o == null)
+          continue;
+        if (o instanceof Object[])
+          for (int j = 0; j < ((Object[]) o).length; j+= 2)
+          appendAttrib(sb, ((Object[]) o)[j], ((Object[]) o)[j + 1]);
+        else
+          appendAttrib(sb, o, attributes[++i]);
+      }
+    sb.append(closer);
+    if (data != null) {
+      if (isCdata)
+        data = wrapCdata(data);
+      sb.appendO(data);
+    }
+    if (doClose)
+      closeTag(sb, name);
+  }
+
+  /**
+   * wrap the string as character data, with replacements for [ noted 
+   * as a list starting with * after the CDATA termination
+   * 
+   * @param data
+   * @return      wrapped text
+   */
+  public static String wrapCdata(Object data) {
+    String s = "" + data;
+    return (s.indexOf("&") < 0 && s.indexOf("<") < 0 ? (s.startsWith("\n") ? "" : "\n") + s 
+        : "<![CDATA[" + PT.rep(s, "]]>", "]]]]><![CDATA[>") + "]]>");
+  }
+  
+  /**
+   * standard <name attr="..." attr="...">data</name>"
+   * 
+   * @param sb
+   * @param name
+   * @param attributes
+   * @param data
+   */
+  public static void appendTagObj(SB sb, String name,
+                               Object[] attributes, Object data) {
+    appendTagAll(sb, name, attributes, data, false, true);
+  }
+
+  /**
+   * standard <name>data</name>"
+   * standard <name attr="..." attr="..."></name>"
+   * 
+   * @param sb
+   * @param name
+   * @param data
+   */
+  public static void appendTag(SB sb, String name, Object data) {
+    if (data instanceof Object[])
+      appendTagAll(sb, name, (Object[]) data, null, false, true);
+    else
+      appendTagAll(sb, name, null, data, false, true);
+  }
+
+  /**
+   * <name><![CDATA[data]]></name>"
+   * 
+   * will convert ]]> to ]] >
+   * 
+   * @param sb
+   * @param name
+   * @param attributes 
+   * @param data
+   */
+  public static void appendCdata(SB sb, String name, 
+                                 Object[] attributes, String data) {
+    appendTagAll(sb, name, attributes, data, true, true);
+  }
+
+  /**
+   * 
+   * @param sb
+   * @param name
+   * @param value
+   */
+  public static void appendAttrib(SB sb, Object name, Object value) {
+    if (value == null)
+      return;
+    
+    // note: <&" are disallowed but not checked for here
+    
+    sb.append(" ").appendO(name).append("=\"").appendO(value).append("\"");
+  }
+
+//  /**
+//   * @param s
+//   * @return   unwrapped text
+//   */
+//  public static String unwrapCdata(String s) {
+//    return (s.startsWith("<![CDATA[") && s.endsWith("]]>") ?
+//        PT.rep(s.substring(9, s.length()-3),"]]]]><![CDATA[>", "]]>") : s);
+//  }
+//  
+
+}
diff --git a/src/javajs/util/ZipData.java b/src/javajs/util/ZipData.java
new file mode 100644 (file)
index 0000000..f20715f
--- /dev/null
@@ -0,0 +1,76 @@
+/* $RCSfile$
+ * $Author$
+ * $Date$
+ * $Revision$
+ *
+ * Copyright (C) 2006  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+package javajs.util;
+
+import java.io.BufferedInputStream;
+
+import javajs.api.GenericZipTools;
+
+
+
+
+public class ZipData {
+  boolean isEnabled = true;
+  byte[] buf;
+  int pt;
+  int nBytes;
+  
+  public ZipData(int nBytes) {
+    this.nBytes = nBytes;
+  }
+  
+  public int addBytes(byte[] byteBuf, int nSectorBytes, int nBytesRemaining) {
+    if (pt == 0) {
+      if (!Rdr.isGzipB(byteBuf)) {
+        isEnabled = false;
+        return -1;
+      }
+      buf = new byte[nBytesRemaining];
+    }
+    int nToAdd = Math.min(nSectorBytes, nBytesRemaining);
+    System.arraycopy(byteBuf, 0, buf, pt, nToAdd);
+    pt += nToAdd;
+    return nBytesRemaining - nToAdd;
+  }    
+
+  public void addTo(GenericZipTools jzt, SB data) {
+    data.append(getGzippedBytesAsString(jzt, buf));
+  }
+
+  static String getGzippedBytesAsString(GenericZipTools jzt, byte[] bytes) {
+    try {
+      BufferedInputStream bis = jzt.getUnGzippedInputStream(bytes);
+      String s = ZipTools.getStreamAsString(bis);
+      bis.close();
+      return s;
+    } catch (Exception e) {
+      return "";
+    }
+  }
+
+}
+
diff --git a/src/javajs/util/ZipTools.java b/src/javajs/util/ZipTools.java
new file mode 100644 (file)
index 0000000..a4082ec
--- /dev/null
@@ -0,0 +1,462 @@
+/* $RCSfile$
+ * $Author$
+ * $Date$
+ * $Revision$
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2006  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+package javajs.util;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.zip.CRC32;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import javajs.api.GenericZipInputStream;
+import javajs.api.GenericZipTools;
+import javajs.api.Interface;
+import javajs.api.ZInputStream;
+
+import org.apache.tools.bzip2.CBZip2InputStreamFactory;
+
+
+/**
+ * Note the JSmol/HTML5 must use its own version of java.util.zip.ZipOutputStream.
+ * 
+ */
+public class ZipTools implements GenericZipTools {
+
+  public ZipTools() {
+    // for reflection
+  }
+  
+  @Override
+  public ZInputStream newZipInputStream(InputStream is) {
+    return newZIS(is);
+  }
+
+  @SuppressWarnings("resource")
+  private static ZInputStream newZIS(InputStream is) {
+    return (is instanceof ZInputStream ? (ZInputStream) is
+        : is instanceof BufferedInputStream ? new GenericZipInputStream(is)
+            : new GenericZipInputStream(new BufferedInputStream(is)));
+  }
+
+  /**
+   * reads a ZIP file and saves all data in a Hashtable so that the files may be
+   * organized later in a different order. Also adds a #Directory_Listing entry.
+   * 
+   * Files are bracketed by BEGIN Directory Entry and END Directory Entry lines,
+   * similar to CompoundDocument.getAllData.
+   * 
+   * @param is
+   * @param subfileList
+   * @param name0
+   *        prefix for entry listing
+   * @param binaryFileList
+   *        |-separated list of files that should be saved as xx xx xx hex byte
+   *        strings. The directory listing is appended with ":asBinaryString"
+   * @param fileData
+   */
+  @Override
+  public void getAllZipData(InputStream is, String[] subfileList,
+                                          String name0, String binaryFileList, String exclude,
+                                          Map<String, String> fileData) {
+    ZipInputStream zis = (ZipInputStream) newZIS(is);
+    ZipEntry ze;
+    SB listing = new SB();
+    binaryFileList = "|" + binaryFileList + "|";
+    String prefix = PT.join(subfileList, '/', 1);
+    String prefixd = null;
+    if (prefix != null) {
+      prefixd = prefix.substring(0, prefix.indexOf("/") + 1);
+      if (prefixd.length() == 0)
+        prefixd = null;
+    }
+    try {
+      while ((ze = zis.getNextEntry()) != null) {
+        String name = ze.getName();
+        if (prefix != null && prefixd != null
+            && !(name.equals(prefix) || name.startsWith(prefixd))
+            || exclude != null && name.contains(exclude))
+          continue;
+        //System.out.println("ziputil: " + name);
+        listing.append(name).appendC('\n');
+        String sname = "|" + name.substring(name.lastIndexOf("/") + 1) + "|";
+        boolean asBinaryString = (binaryFileList.indexOf(sname) >= 0);
+        byte[] bytes = Rdr.getLimitedStreamBytes(zis, ze.getSize());
+        String str;
+        if (asBinaryString) {
+          str = getBinaryStringForBytes(bytes);
+          name += ":asBinaryString";
+        } else {
+          str = Rdr.fixUTF(bytes);
+        }
+        str = "BEGIN Directory Entry " + name + "\n" + str
+            + "\nEND Directory Entry " + name + "\n";
+        String key = name0 + "|" + name;
+        fileData.put(key, str);
+      }
+    } catch (Exception e) {
+    }
+    fileData.put("#Directory_Listing", listing.toString());
+  }
+
+  private String getBinaryStringForBytes(byte[] bytes) {
+    SB ret = new SB();
+    for (int i = 0; i < bytes.length; i++)
+      ret.append(Integer.toHexString(bytes[i] & 0xFF)).appendC(' ');
+    return ret.toString();
+  }
+
+  /**
+   * iteratively drills into zip files of zip files to extract file content or
+   * zip file directory. Also works with JAR files.
+   * 
+   * Does not return "__MACOS" paths
+   * 
+   * @param bis
+   * @param list
+   * @param listPtr
+   * @param asBufferedInputStream
+   *        for Pmesh
+   * @return directory listing or subfile contents
+   */
+  @Override
+  public Object getZipFileDirectory(BufferedInputStream bis, String[] list,
+                                    int listPtr, boolean asBufferedInputStream) {
+    SB ret;
+    if (list == null || listPtr >= list.length)
+      return getZipDirectoryAsStringAndClose(bis);
+    bis = Rdr.getPngZipStream(bis, true);
+    String fileName = list[listPtr];
+    ZipInputStream zis = new ZipInputStream(bis);
+    ZipEntry ze;
+    //System.out.println("fname=" + fileName);
+    try {
+      boolean isAll = (fileName.equals("."));
+      if (isAll || fileName.lastIndexOf("/") == fileName.length() - 1) {
+        ret = new SB();
+        while ((ze = zis.getNextEntry()) != null) {
+          String name = ze.getName();
+          if (isAll || name.startsWith(fileName))
+            ret.append(name).appendC('\n');
+        }
+        String str = ret.toString();
+        return (asBufferedInputStream ? Rdr.getBIS(str.getBytes()) : str);
+      }
+      int pt = fileName.indexOf(":asBinaryString");
+      boolean asBinaryString = (pt > 0);
+      if (asBinaryString)
+        fileName = fileName.substring(0, pt);
+      fileName = fileName.replace('\\', '/');
+      while ((ze = zis.getNextEntry()) != null
+          && !fileName.equals(ze.getName())) {
+      }
+      byte[] bytes = (ze == null ? null : Rdr.getLimitedStreamBytes(zis,
+          ze.getSize()));
+      ze = null;
+      zis.close();
+      if (bytes == null)
+        return "";
+      if (Rdr.isZipB(bytes) || Rdr.isPngZipB(bytes))
+        return getZipFileDirectory(Rdr.getBIS(bytes), list, ++listPtr,
+            asBufferedInputStream);
+      if (asBufferedInputStream)
+        return Rdr.getBIS(bytes);
+      if (asBinaryString) {
+        ret = new SB();
+        for (int i = 0; i < bytes.length; i++)
+          ret.append(Integer.toHexString(bytes[i] & 0xFF)).appendC(' ');
+        return ret.toString();
+      }
+      if (Rdr.isGzipB(bytes))
+        bytes = Rdr.getLimitedStreamBytes(getUnGzippedInputStream(bytes), -1);
+      return Rdr.fixUTF(bytes);
+    } catch (Exception e) {
+      return "";
+    }
+  }
+
+  @Override
+  public byte[] getZipFileContentsAsBytes(BufferedInputStream bis,
+                                          String[] list, int listPtr) {
+    byte[] ret = new byte[0];
+    String fileName = list[listPtr];
+    if (fileName.lastIndexOf("/") == fileName.length() - 1)
+      return ret;
+    try {
+      bis = Rdr.getPngZipStream(bis, true);
+      ZipInputStream zis = new ZipInputStream(bis);
+      ZipEntry ze;
+      while ((ze = zis.getNextEntry()) != null) {
+        if (!fileName.equals(ze.getName()))
+          continue;
+        byte[] bytes = Rdr.getLimitedStreamBytes(zis, ze.getSize());
+        return ((Rdr.isZipB(bytes) || Rdr.isPngZipB(bytes)) && ++listPtr < list.length ? getZipFileContentsAsBytes(
+            Rdr.getBIS(bytes), list, listPtr) : bytes);
+      }
+    } catch (Exception e) {
+    }
+    return ret;
+  }
+  
+  @Override
+  public String getZipDirectoryAsStringAndClose(BufferedInputStream bis) {
+    SB sb = new SB();
+    String[] s = new String[0];
+    try {
+      s = getZipDirectoryOrErrorAndClose(bis, null);
+      bis.close();
+    } catch (Exception e) {
+      System.out.println(e.toString());
+    }
+    for (int i = 0; i < s.length; i++)
+      sb.append(s[i]).appendC('\n');
+    return sb.toString();
+  }
+
+  @Override
+  public String[] getZipDirectoryAndClose(BufferedInputStream bis,
+                                                 String manifestID) {
+    String[] s = new String[0];
+    try {
+      s = getZipDirectoryOrErrorAndClose(bis, manifestID);
+      bis.close();
+    } catch (Exception e) {
+      System.out.println(e.toString());
+    }
+    return s;
+  }
+
+  private String[] getZipDirectoryOrErrorAndClose(BufferedInputStream bis,
+                                                  String manifestID)
+      throws IOException {
+    bis = Rdr.getPngZipStream(bis, true);
+    Lst<String> v = new Lst<String>();
+    ZipInputStream zis = new ZipInputStream(bis);
+    ZipEntry ze;
+    String manifest = null;
+    while ((ze = zis.getNextEntry()) != null) {
+      String fileName = ze.getName();
+      if (manifestID != null && fileName.startsWith(manifestID))
+        manifest = getStreamAsString(zis);
+      else if (!fileName.startsWith("__MACOS")) // resource fork not nec.
+        v.addLast(fileName);
+    }
+    zis.close();
+    if (manifestID != null)
+      v.add(0, manifest == null ? "" : manifest + "\n############\n");
+    return v.toArray(new String[v.size()]);
+  }
+
+  public static String getStreamAsString(InputStream is) throws IOException {
+    return Rdr.fixUTF(Rdr.getLimitedStreamBytes(is, -1));
+  }
+
+  @Override
+  public InputStream newGZIPInputStream(InputStream is) throws IOException {
+    return new BufferedInputStream(new GZIPInputStream(is, 512));
+  }
+
+  @Override
+  public InputStream newBZip2InputStream(InputStream is) throws IOException {
+    return new BufferedInputStream(((CBZip2InputStreamFactory) Interface.getInterface("org.apache.tools.bzip2.CBZip2InputStreamFactory")).getStream(is));
+  }
+
+  @Override
+  public BufferedInputStream getUnGzippedInputStream(byte[] bytes) {
+    try {
+      return Rdr.getUnzippedInputStream(this, Rdr.getBIS(bytes));
+    } catch (Exception e) {
+      return null;
+    }
+  }
+
+  @Override
+  public void addZipEntry(Object zos, String fileName) throws IOException {
+    ((ZipOutputStream) zos).putNextEntry(new ZipEntry(fileName));
+  }
+
+  @Override
+  public void closeZipEntry(Object zos) throws IOException {
+    ((ZipOutputStream) zos).closeEntry();
+  }
+
+  @Override
+  public Object getZipOutputStream(Object bos) {
+//    /**
+//     * @j2sNative
+//     * 
+//     *            return javajs.api.Interface.getInterface(
+//     *            "java.util.zip.ZipOutputStream").setZOS(bos);
+//     * 
+//     */
+//    {
+      return new ZipOutputStream((OutputStream) bos);
+//    }
+  }
+
+  @Override
+  public int getCrcValue(byte[] bytes) {
+    CRC32 crc = new CRC32();
+    crc.update(bytes, 0, bytes.length);
+    return (int) crc.getValue();
+  }
+
+  @Override
+  public void readFileAsMap(BufferedInputStream bis, Map<String, Object> bdata, String name) {
+       readFileAsMapStatic(bis, bdata, name);
+  }
+
+  public static void readFileAsMapStatic(BufferedInputStream bis,
+                       Map<String, Object> bdata, String name) {
+    int pt = (name == null ? -1 : name.indexOf("|"));
+    name = (pt >= 0 ? name.substring(pt + 1) : null);
+    try {
+      if (Rdr.isPngZipStream(bis)) {
+        boolean isImage = "_IMAGE_".equals(name);
+        if (name == null || isImage)
+          bdata.put((isImage ? "_DATA_" : "_IMAGE_"), new BArray(getPngImageBytes(bis)));
+        if (!isImage)
+          cacheZipContentsStatic(bis, name, bdata, true);
+      } else if (Rdr.isZipS(bis)) {
+        cacheZipContentsStatic(bis, name, bdata, true);
+      } else if (name == null){
+        bdata.put("_DATA_", new BArray(Rdr.getLimitedStreamBytes(bis, -1)));
+      } else {
+        throw new IOException("ZIP file " + name + " not found");
+      }
+      bdata.put("$_BINARY_$", Boolean.TRUE);
+    } catch (IOException e) {
+      bdata.clear();
+      bdata.put("_ERROR_", e.getMessage());
+    }
+  }
+
+  @Override
+  public String cacheZipContents(BufferedInputStream bis,
+                                        String fileName,
+                                        Map<String, Object> cache, 
+                                        boolean asByteArray) {
+               return cacheZipContentsStatic(bis, fileName, cache, asByteArray);
+  }
+
+       /**
+        * 
+        * @param bis
+        * @param fileName may end with "/" for a prefix or contain "|xxxx.xxx" for a specific file or be null
+        * @param cache
+        * @param asByteArray
+        * @return
+        */
+  public static String cacheZipContentsStatic(BufferedInputStream bis,
+                       String fileName, Map<String, Object> cache, boolean asByteArray) {
+    ZipInputStream zis = (ZipInputStream) newZIS(bis);
+    ZipEntry ze;
+    SB listing = new SB();
+    long n = 0;
+    boolean isPath = (fileName != null && fileName.endsWith("/"));
+    boolean oneFile = (asByteArray && !isPath && fileName != null);
+    int pt = (oneFile ? fileName.indexOf("|") : -1);
+    String file0 = (pt >= 0 ? fileName : null);
+    if (pt >= 0)
+      fileName = fileName.substring(0,  pt);
+    String prefix = (fileName == null ? "" : isPath ? fileName : fileName + "|");
+    try {
+      while ((ze = zis.getNextEntry()) != null) {
+        String name = ze.getName();
+        if (fileName != null) {
+          if (oneFile) {
+            if (!name.equalsIgnoreCase(fileName))
+              continue;
+          } else {
+            listing.append(name).appendC('\n');
+          }
+        }
+        long nBytes = ze.getSize();
+        byte[] bytes = Rdr.getLimitedStreamBytes(zis, nBytes);
+        if (file0 != null) {
+          readFileAsMapStatic(Rdr.getBIS(bytes), cache, file0);
+          return null;
+        }
+        n += bytes.length;
+        Object o = (asByteArray ? new BArray(bytes) : bytes);        
+        cache.put((oneFile ? "_DATA_" : prefix + name), o);
+        if (oneFile)
+          break;
+      }
+      zis.close();
+    } catch (Exception e) {
+      try {
+        zis.close();
+      } catch (IOException e1) {
+      }
+      return null;
+    }
+    if (n == 0 || fileName == null)
+      return null;
+    System.out.println("ZipTools cached " + n + " bytes from " + fileName);
+    return listing.toString();
+  }
+
+  private static byte[] getPngImageBytes(BufferedInputStream bis) {
+    try {
+      if (Rdr.isPngZipStream(bis)) {
+        int pt_count[] = new int[2];
+        Rdr.getPngZipPointAndCount(bis, pt_count);
+        if (pt_count[1] != 0)
+          return deActivatePngZipB(Rdr.getLimitedStreamBytes(bis, pt_count[0]));
+      }
+      return Rdr.getLimitedStreamBytes(bis, -1);
+    } catch (IOException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Once a PNGJ image has been extracted, we want to red-line its
+   * iTXt "Jmol Type PNGJ" tag, since it is no longer associated with
+   * ZIP data.
+   *  
+   * @param bytes
+   * @return disfigured bytes
+   * 
+   */
+  private static byte[] deActivatePngZipB(byte[] bytes) {
+    // \0PNGJ starting at byte 50 changed to \0 NGJ
+    if (Rdr.isPngZipB(bytes))
+      bytes[51] = 32;
+    return bytes;
+  }
+
+
+
+}
diff --git a/src/org/apache/harmony/luni/util/ExternalMessages.properties b/src/org/apache/harmony/luni/util/ExternalMessages.properties
new file mode 100644 (file)
index 0000000..54227b8
--- /dev/null
@@ -0,0 +1,311 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# External Messages for EN locale
+K0006=Negative index specified
+K0007=attempt to write after finish
+K0008=Cannot read version
+K0009=Missing version string\: {0}
+K000a=Entry is not named
+K000b=Invalid attribute {0}
+K000c=cannot resolve subclasses
+K000d=Unknown attribute
+K000e=Cannot add attributes to empty string
+K0014=Unquoted {0} in suffix\: {1}
+K0015=Unexpected {0} in fraction\: {1}
+K0016=Unexpected {0} in {1}
+K0017=Missing pattern before {0} in {1}
+K0018=Missing exponent format {0}
+K0019=Unterminated quote {0}
+K001a=Missing grouping format {0}
+K001b=Invalid exponent format {0}
+K001c=Invalid pattern char {0} in {1}
+K001d=Invalid argument number
+K001e=Missing element format
+K001f=Unknown element format
+K0020=Unknown format
+K002b=Unknown pattern character - '{0}'
+K002c=Access denied {0}
+K002e=Offset out of bounds
+K002f=Arguments out of bounds
+K0032=Address null or destination port out of range
+K0033=Unknown socket type
+K0034=Packet address mismatch with connected address
+K0035=Zero or negative buffer size
+K0036=Invalid negative timeout
+K0037=Connection already established
+K0038=No host name provided
+K0039=Attempted to join a non-multicast group
+K003a=Attempted to leave a non-multicast group
+K003c=TimeToLive out of bounds
+K003d=Socket is closed
+K003e=SOCKS connection failed\: {0}
+K003f=Unable to connect to SOCKS server\: {0}
+K0040=Invalid SOCKS client.
+K0041=Socket implementation does not support SOCKS.
+K0042=Socket implementation factory already set
+K0044=The factory has already been set
+K0045=Attempted to set a negative SoLinger
+K0046=Local port declared out of range
+K0047=buffer is null
+K0048=Invalid action specified\: {0}
+K0049=MinPort is greater than MaxPort
+K004a=Invalid port number specified
+K004b=Attempt to set factory more than once.
+K004c=Package is sealed
+K004d=Does not support writing to the input stream
+K004e=Duplicate Factory
+K004f=rounding necessary
+K0050=wrong rounding mode
+K0051=scale value < than zero
+K0052=Array index out of range\: {0}
+K0053=Package {0} already defined.
+K0055=String index out of range\: {0}
+K0056=Already destroyed
+K0057=Has threads
+K0058=size must be > 0
+K0059=Stream is closed
+K005a=Mark has been invalidated.
+K005b=BufferedReader is closed
+K005c=Invalid Mark.
+K005d=Writer is closed.
+K005e=size must be >\= 0
+K005f=Does not support writing to the output stream
+K0060=CharArrayReader is closed.
+K0062=Second byte at {0} does not match UTF8 Specification
+K0063=Third byte at {0} does not match UTF8 Specification
+K0064=Second or third byte at {0} does not match UTF8 Specification
+K0065=Input at {0} does not match UTF8 Specification
+K0066=Entry already exists: {0}
+K0068=String is too long
+K0069=File cannot compare to non File
+K006a=time must be positive
+K006b=Prefix must be at least 3 characters
+K006c=FileDescriptor is null
+K006d=actions invalid
+K006e=path is null
+K006f=invalid permission\: {0}
+K0070=InputStreamReader is closed.
+K0071=Error fetching SUID\: {0}
+K0072={0} computing SHA-1 / SUID
+K0073=OutputStreamWriter is closed.
+K0074=Not connected
+K0075=InputStream is closed
+K0076=Pipe broken
+K0077=Crc mismatch
+K0078=Pipe is closed
+K0079=Already connected
+K007a=Pipe already connected
+K007b=Pipe Not Connected
+K007e=Pushback buffer full
+K007f=Mark/Reset not supported
+K0080=Reader is closed
+K0081=Mode must be one of "r" or "rw"
+K0083=StringReader is closed.
+K0084=can only instantiate one BootstrapClassLoader
+K0086=Referenced reflect object is no longer valid
+K0087=Referenced reflect object is no longer valid\: {0}
+K0088=Incorrect end of BER tag
+K0089=Unknown type\: {0}
+K008a=Read {0} bytes trying to read {1} bytes from {2}
+K008b=Position\: {0}
+K008c=Invalid Base64 char\:{0}
+K008d=This protocol does not support input
+K008e=Does not support output
+K008f=This method does not support writing\: {0}
+K0090=can't open OutputStream after reading from an inputStream
+K0091=Cannot access request header fields after connection is set
+K0092=Cannot set method after connection is made
+K0093=Too many redirects
+K0094=Unable to change directories
+K0095=Could not establish data connection
+K0096=Unable to retrieve file\: {0}
+K0097=Unable to connect to server\: {0}
+K0098=Unable to log into server\: {0}
+K0099=Unable to configure data port
+K009a=Unable to store file
+K009b=Unable to set transfer type
+K00a2=Parsing policy file\: {0}, expected quoted {1}, found unquoted\: {2}
+K00a3=Parsing policy file\: {0}, found unexpected\: {1}
+K00a4=Content-Length underflow
+K00a5=Invalid parameter - {0}
+K00a8=Parsing policy file\: {0}, invalid codesource URL\: {1}
+K00ab=No active entry
+K00ae=Size mismatch
+K00af=Invalid proxy port\: {0}
+K00b0=Proxy port out of range
+K00b1=Invalid port number
+K00b2=Content-Length exceeded
+K00b3=Unknown protocol\: {0}
+K00b6=No entries
+K00b7=File is closed
+K00c1=Illegal character
+K00cd=Failure to connect to SOCKS server.
+K00ce=Unable to connect to identd to verify user.
+K00cf=Failure - user ids do not match.
+K00d0=Success
+K00d1=Read null attempting to read class descriptor for an object
+K00d2=Wrong format\: 0x{0}
+K00d3=Read an exception
+K00d4={2} - {0} not compatible with {1}
+K00d5=Invalid typecode\: {0}
+K00d7=Wrong base type in\: {0}
+K00d8=Protocol not found\: {0}
+K00d9=Callback object cannot be null
+K00da=Incompatible class (SUID)\: {0} but expected {1}
+K00dc=IllegalAccessException
+K00e3=Could not create specified security manager\: {0}
+K00e4=Key usage is critical and cannot be used for digital signature purposes.
+K00e5=month\: {0}
+K00e6=day of month\: {0}
+K00e7=day of week\: {0}
+K00e8=time\: {0}
+K00e9=DST offset\: {0}
+K00ea=era\: {0}
+K00eb={0} failed verification of {1}
+K00ec={0} has invalid digest for {1} in {2}
+K00ed={0} is not an interface
+K00ee={0} is not visible from class loader
+K00ef={0} appears more than once
+K00f0=non-public interfaces must be in the same package
+K00f1=not a proxy instance
+K00f2=the methods named {0} must have the same return type
+K00f3=Timer was cancelled
+K00f5=Illegal delay to start the TimerTask
+K00f6=TimerTask is scheduled already
+K00f7=TimerTask is cancelled
+K00f8=day of week in month\: {0}
+K00f9=min or max digit count too large
+K00fa=min digits greater than max digits
+K00fb=min or max digits negative
+K00fc=Jar entry not specified
+K00fd=Invalid keystore
+K00fe=Incorrect password
+K0185=The alias already exists for a key entry.
+K018f=Can't convert to BMPString \: {0}
+K0190=No data to decode
+K0191=Invalid size, must be a multiple of 64 from 512 to 1024
+K0193=An identity with this name already exists in this scope
+K0194=An identity in the scope has the same public key
+K0195=The existing public key and the one contained in the certificate do not match.
+K0196=Certificate is missing
+K0199=Count out of range
+K01a0=End of stream condition
+K01a4=Already shutting down
+K01a5=Illegal shutdown hook\: {0}
+K01a6=Invalid filter
+K01a7=Name too long: {0}
+K01b3=Incorrect number of arguments
+K01b4=Cannot convert {0} to {1}
+K01b6=Cannot find \!/
+K01c1=File is a Directory
+K01c2=Cannot create\: {0}
+K01c3=Unable to open\: {0}
+K01c4=Invalid zip file\: {0}
+K01c6=No Main-Class specified in manifest\: {0}
+K01d1=Signers of '{0}' do not match signers of other classes in package
+K01d2={1} - protected system package '{0}'
+K01ec=key size must be a multiple of 8 bits
+K01ed=key size must be at least 512 bits
+K01fe=Incomplete % sequence at\: {0}
+K01ff=Invalid % sequence ({0}) at\: {1}
+K0220=UTFDataFormatException
+K0222=No Manifest found in jar file\: {0}
+K0300=Unsupported encoding
+K0301=Not signed data
+K0302=Relative path
+K0303=Scheme-specific part expected
+K0304=Authority expected
+K0305=Illegal character in scheme
+K0306={0} in schemeSpecificPart
+K0307={0} in authority
+K0308={0} in path
+K0309={0} in query
+K030a={0} in fragment
+K030c=Expected host
+K030d=Illegal character in userinfo
+K030e=Expected a closing square bracket for ipv6 address
+K030f=Malformed ipv6 address
+K0310=Illegal character in host name
+K0311=Malformed ipv4 address
+K0312=URI is not absolute
+K0313=Incomplete % sequence
+K0314=Invalid % sequence ({0})
+K0315=Socket is already bound
+K0316=SocketAddress {0} not supported
+K0317=Host is unresolved\: {0}
+K0318=SocketAddress is null
+K0319=Exception in thread "{0}"\ 
+K031a=URI is not absolute\: {0}
+K031b=URI is not hierarchical\: {0}
+K031c=Expected file scheme in URI\: {0}
+K031d=Expected non-empty path in URI\: {0}
+K031e=Found {0} component in URI\: {1}
+K031f=Socket is not bound
+K0320=Socket is not connected
+K0321=Socket input is shutdown
+K0322=Not a supported ISO 4217 Currency Code\: {0}
+K0323=Not a supported ISO 3166 Country locale\: {0}
+K0324=Needs dictionary
+K0325=Port out of range\: {0}
+K0326={0} at index {1}\: {2}
+K0327={0}\: {1}
+K0328=Certificate not yet valid
+K0329=Certificate expired
+K0330=interface name is null
+K0331=address is null
+K0332=Invalid IP Address is neither 4 or 16 bytes\: {0}
+K0333=Urgent data not supported
+K0334=Cannot set network interface with null
+K0335=No addresses associated with Interface
+K0337=null type not allowed
+K0338=Address not associated with an interface - not set
+K0339=Invalid IP Address is neither 4 or 16 bytes
+K0340={0} incompatible with {1}
+K0342=Scheme expected
+K0344=Not a valid {0}, subclass should override readResolve()
+K0346=Unmatched braces in the pattern
+K0347=seek position is negative
+K0348=Format specifier '{0}'
+K0349=Conversion is '{0}'
+K034a=The flags are {0}
+K034b=url and proxy can not be null
+K034c=proxy should not be null
+K034d=method has not been implemented yet
+K034e=Build rules empty
+K0351=format is null
+KA000=Line too long
+KA001=Argument must not be null
+KA002=Unshared read of back reference
+KA003=different mode already set
+KA004=Enums may not be cloned
+KA005={0} is not an enum type
+KA006={0} is not a constant in the enum type {1}
+KA007=field is null
+KA008={0} is an illegal radix
+KA009=CharsetName is illegal
+KA00a=File is null
+KA00b=InputStream is null
+KA00c=Readable is null
+KA00d=ReadableByteChannel is null
+KA00e=Radix {0} is less than Character.MIN_RADIX or greater than Character.MAX_RADIX
+KA00f=Socket output is shutdown
+KA010=Cannot read back reference to unshared object
+KA011=Malformed reply from SOCKS server
+KA012=No such file or directory
+KA013=Number of bytes to skip cannot be negative
+KA014=Invalit UUID string
+KA015=Incompatible class (base name)\: {0} but expected {1}
+
diff --git a/src/org/apache/harmony/luni/util/ExternalMessages.properties.js b/src/org/apache/harmony/luni/util/ExternalMessages.properties.js
new file mode 100644 (file)
index 0000000..cf6a86e
--- /dev/null
@@ -0,0 +1 @@
+java.util.ResourceBundle.registerBundle("org.apache.harmony.luni.util.ExternalMessages", "K0006=Negative index specified\r\nK0007=attempt to write after finish\r\nK0008=Cannot read version\r\nK0009=Missing version string\\: {0}\r\nK000a=Entry is not named\r\nK000b=Invalid attribute {0}\r\nK000c=cannot resolve subclasses\r\nK000d=Unknown attribute\r\nK000e=Cannot add attributes to empty string\r\nK0014=Unquoted {0} in suffix\\: {1}\r\nK0015=Unexpected {0} in fraction\\: {1}\r\nK0016=Unexpected {0} in {1}\r\nK0017=Missing pattern before {0} in {1}\r\nK0018=Missing exponent format {0}\r\nK0019=Unterminated quote {0}\r\nK001a=Missing grouping format {0}\r\nK001b=Invalid exponent format {0}\r\nK001c=Invalid pattern char {0} in {1}\r\nK001d=Invalid argument number\r\nK001e=Missing element format\r\nK001f=Unknown element format\r\nK0020=Unknown format\r\nK002b=Unknown pattern character - \'{0}\'\r\nK002c=Access denied {0}\r\nK002e=Offset out of bounds\r\nK002f=Arguments out of bounds\r\nK0032=Address null or destination port out of range\r\nK0033=Unknown socket type\r\nK0034=Packet address mismatch with connected address\r\nK0035=Zero or negative buffer size\r\nK0036=Invalid negative timeout\r\nK0037=Connection already established\r\nK0038=No host name provided\r\nK0039=Attempted to join a non-multicast group\r\nK003a=Attempted to leave a non-multicast group\r\nK003c=TimeToLive out of bounds\r\nK003d=Socket is closed\r\nK003e=SOCKS connection failed\\: {0}\r\nK003f=Unable to connect to SOCKS server\\: {0}\r\nK0040=Invalid SOCKS client.\r\nK0041=Socket implementation does not support SOCKS.\r\nK0042=Socket implementation factory already set\r\nK0044=The factory has already been set\r\nK0045=Attempted to set a negative SoLinger\r\nK0046=Local port declared out of range\r\nK0047=buffer is null\r\nK0048=Invalid action specified\\: {0}\r\nK0049=MinPort is greater than MaxPort\r\nK004a=Invalid port number specified\r\nK004b=Attempt to set factory more than once.\r\nK004c=Package is sealed\r\nK004d=Does not support writing to the input stream\r\nK004e=Duplicate Factory\r\nK004f=rounding necessary\r\nK0050=wrong rounding mode\r\nK0051=scale value < than zero\r\nK0052=Array index out of range\\: {0}\r\nK0053=Package {0} already defined.\r\nK0055=String index out of range\\: {0}\r\nK0056=Already destroyed\r\nK0057=Has threads\r\nK0058=size must be > 0\r\nK0059=Stream is closed\r\nK005a=Mark has been invalidated.\r\nK005b=BufferedReader is closed\r\nK005c=Invalid Mark.\r\nK005d=Writer is closed.\r\nK005e=size must be >\\= 0\r\nK005f=Does not support writing to the output stream\r\nK0060=CharArrayReader is closed.\r\nK0062=Second byte at {0} does not match UTF8 Specification\r\nK0063=Third byte at {0} does not match UTF8 Specification\r\nK0064=Second or third byte at {0} does not match UTF8 Specification\r\nK0065=Input at {0} does not match UTF8 Specification\r\nK0066=Entry already exists: {0}\r\nK0068=String is too long\r\nK0069=File cannot compare to non File\r\nK006a=time must be positive\r\nK006b=Prefix must be at least 3 characters\r\nK006c=FileDescriptor is null\r\nK006d=actions invalid\r\nK006e=path is null\r\nK006f=invalid permission\\: {0}\r\nK0070=InputStreamReader is closed.\r\nK0071=Error fetching SUID\\: {0}\r\nK0072={0} computing SHA-1 / SUID\r\nK0073=OutputStreamWriter is closed.\r\nK0074=Not connected\r\nK0075=InputStream is closed\r\nK0076=Pipe broken\r\nK0077=Crc mismatch\r\nK0078=Pipe is closed\r\nK0079=Already connected\r\nK007a=Pipe already connected\r\nK007b=Pipe Not Connected\r\nK007e=Pushback buffer full\r\nK007f=Mark/Reset not supported\r\nK0080=Reader is closed\r\nK0081=Mode must be one of \"r\" or \"rw\"\r\nK0083=StringReader is closed.\r\nK0084=can only instantiate one BootstrapClassLoader\r\nK0086=Referenced reflect object is no longer valid\r\nK0087=Referenced reflect object is no longer valid\\: {0}\r\nK0088=Incorrect end of BER tag\r\nK0089=Unknown type\\: {0}\r\nK008a=Read {0} bytes trying to read {1} bytes from {2}\r\nK008b=Position\\: {0}\r\nK008c=Invalid Base64 char\\:{0}\r\nK008d=This protocol does not support input\r\nK008e=Does not support output\r\nK008f=This method does not support writing\\: {0}\r\nK0090=can\'t open OutputStream after reading from an inputStream\r\nK0091=Cannot access request header fields after connection is set\r\nK0092=Cannot set method after connection is made\r\nK0093=Too many redirects\r\nK0094=Unable to change directories\r\nK0095=Could not establish data connection\r\nK0096=Unable to retrieve file\\: {0}\r\nK0097=Unable to connect to server\\: {0}\r\nK0098=Unable to log into server\\: {0}\r\nK0099=Unable to configure data port\r\nK009a=Unable to store file\r\nK009b=Unable to set transfer type\r\nK00a2=Parsing policy file\\: {0}, expected quoted {1}, found unquoted\\: {2}\r\nK00a3=Parsing policy file\\: {0}, found unexpected\\: {1}\r\nK00a4=Content-Length underflow\r\nK00a5=Invalid parameter - {0}\r\nK00a8=Parsing policy file\\: {0}, invalid codesource URL\\: {1}\r\nK00ab=No active entry\r\nK00ae=Size mismatch\r\nK00af=Invalid proxy port\\: {0}\r\nK00b0=Proxy port out of range\r\nK00b1=Invalid port number\r\nK00b2=Content-Length exceeded\r\nK00b3=Unknown protocol\\: {0}\r\nK00b6=No entries\r\nK00b7=File is closed\r\nK00c1=Illegal character\r\nK00cd=Failure to connect to SOCKS server.\r\nK00ce=Unable to connect to identd to verify user.\r\nK00cf=Failure - user ids do not match.\r\nK00d0=Success\r\nK00d1=Read null attempting to read class descriptor for an object\r\nK00d2=Wrong format\\: 0x{0}\r\nK00d3=Read an exception\r\nK00d4={2} - {0} not compatible with {1}\r\nK00d5=Invalid typecode\\: {0}\r\nK00d7=Wrong base type in\\: {0}\r\nK00d8=Protocol not found\\: {0}\r\nK00d9=Callback object cannot be null\r\nK00da=Incompatible class (SUID)\\: {0} but expected {1}\r\nK00dc=IllegalAccessException\r\nK00e3=Could not create specified security manager\\: {0}\r\nK00e4=Key usage is critical and cannot be used for digital signature purposes.\r\nK00e5=month\\: {0}\r\nK00e6=day of month\\: {0}\r\nK00e7=day of week\\: {0}\r\nK00e8=time\\: {0}\r\nK00e9=DST offset\\: {0}\r\nK00ea=era\\: {0}\r\nK00eb={0} failed verification of {1}\r\nK00ec={0} has invalid digest for {1} in {2}\r\nK00ed={0} is not an interface\r\nK00ee={0} is not visible from class loader\r\nK00ef={0} appears more than once\r\nK00f0=non-public interfaces must be in the same package\r\nK00f1=not a proxy instance\r\nK00f2=the methods named {0} must have the same return type\r\nK00f3=Timer was cancelled\r\nK00f5=Illegal delay to start the TimerTask\r\nK00f6=TimerTask is scheduled already\r\nK00f7=TimerTask is cancelled\r\nK00f8=day of week in month\\: {0}\r\nK00f9=min or max digit count too large\r\nK00fa=min digits greater than max digits\r\nK00fb=min or max digits negative\r\nK00fc=Jar entry not specified\r\nK00fd=Invalid keystore\r\nK00fe=Incorrect password\r\nK0185=The alias already exists for a key entry.\r\nK018f=Can\'t convert to BMPString \\: {0}\r\nK0190=No data to decode\r\nK0191=Invalid size, must be a multiple of 64 from 512 to 1024\r\nK0193=An identity with this name already exists in this scope\r\nK0194=An identity in the scope has the same public key\r\nK0195=The existing public key and the one contained in the certificate do not match.\r\nK0196=Certificate is missing\r\nK0199=Count out of range\r\nK01a0=End of stream condition\r\nK01a4=Already shutting down\r\nK01a5=Illegal shutdown hook\\: {0}\r\nK01a6=Invalid filter\r\nK01a7=Name too long: {0}\r\nK01b3=Incorrect number of arguments\r\nK01b4=Cannot convert {0} to {1}\r\nK01b6=Cannot find \\!/\r\nK01c1=File is a Directory\r\nK01c2=Cannot create\\: {0}\r\nK01c3=Unable to open\\: {0}\r\nK01c4=Invalid zip file\\: {0}\r\nK01c6=No Main-Class specified in manifest\\: {0}\r\nK01d1=Signers of \'{0}\' do not match signers of other classes in package\r\nK01d2={1} - protected system package \'{0}\'\r\nK01ec=key size must be a multiple of 8 bits\r\nK01ed=key size must be at least 512 bits\r\nK01fe=Incomplete % sequence at\\: {0}\r\nK01ff=Invalid % sequence ({0}) at\\: {1}\r\nK0220=UTFDataFormatException\r\nK0222=No Manifest found in jar file\\: {0}\r\nK0300=Unsupported encoding\r\nK0301=Not signed data\r\nK0302=Relative path\r\nK0303=Scheme-specific part expected\r\nK0304=Authority expected\r\nK0305=Illegal character in scheme\r\nK0306={0} in schemeSpecificPart\r\nK0307={0} in authority\r\nK0308={0} in path\r\nK0309={0} in query\r\nK030a={0} in fragment\r\nK030c=Expected host\r\nK030d=Illegal character in userinfo\r\nK030e=Expected a closing square bracket for ipv6 address\r\nK030f=Malformed ipv6 address\r\nK0310=Illegal character in host name\r\nK0311=Malformed ipv4 address\r\nK0312=URI is not absolute\r\nK0313=Incomplete % sequence\r\nK0314=Invalid % sequence ({0})\r\nK0315=Socket is already bound\r\nK0316=SocketAddress {0} not supported\r\nK0317=Host is unresolved\\: {0}\r\nK0318=SocketAddress is null\r\nK0319=Exception in thread \"{0}\"\\ \r\nK031a=URI is not absolute\\: {0}\r\nK031b=URI is not hierarchical\\: {0}\r\nK031c=Expected file scheme in URI\\: {0}\r\nK031d=Expected non-empty path in URI\\: {0}\r\nK031e=Found {0} component in URI\\: {1}\r\nK031f=Socket is not bound\r\nK0320=Socket is not connected\r\nK0321=Socket input is shutdown\r\nK0322=Not a supported ISO 4217 Currency Code\\: {0}\r\nK0323=Not a supported ISO 3166 Country locale\\: {0}\r\nK0324=Needs dictionary\r\nK0325=Port out of range\\: {0}\r\nK0326={0} at index {1}\\: {2}\r\nK0327={0}\\: {1}\r\nK0328=Certificate not yet valid\r\nK0329=Certificate expired\r\nK0330=interface name is null\r\nK0331=address is null\r\nK0332=Invalid IP Address is neither 4 or 16 bytes\\: {0}\r\nK0333=Urgent data not supported\r\nK0334=Cannot set network interface with null\r\nK0335=No addresses associated with Interface\r\nK0337=null type not allowed\r\nK0338=Address not associated with an interface - not set\r\nK0339=Invalid IP Address is neither 4 or 16 bytes\r\nK0340={0} incompatible with {1}\r\nK0342=Scheme expected\r\nK0344=Not a valid {0}, subclass should override readResolve()\r\nK0346=Unmatched braces in the pattern\r\nK0347=seek position is negative\r\nK0348=Format specifier \'{0}\'\r\nK0349=Conversion is \'{0}\'\r\nK034a=The flags are {0}\r\nK034b=url and proxy can not be null\r\nK034c=proxy should not be null\r\nK034d=method has not been implemented yet\r\nK034e=Build rules empty\r\nK0351=format is null\r\nKA000=Line too long\r\nKA001=Argument must not be null\r\nKA002=Unshared read of back reference\r\nKA003=different mode already set\r\nKA004=Enums may not be cloned\r\nKA005={0} is not an enum type\r\nKA006={0} is not a constant in the enum type {1}\r\nKA007=field is null\r\nKA008={0} is an illegal radix\r\nKA009=CharsetName is illegal\r\nKA00a=File is null\r\nKA00b=InputStream is null\r\nKA00c=Readable is null\r\nKA00d=ReadableByteChannel is null\r\nKA00e=Radix {0} is less than Character.MIN_RADIX or greater than Character.MAX_RADIX\r\nKA00f=Socket output is shutdown\r\nKA010=Cannot read back reference to unshared object\r\nKA011=Malformed reply from SOCKS server\r\nKA012=No such file or directory\r\nKA013=Number of bytes to skip cannot be negative\r\nKA014=Invalit UUID string\r\nKA015=Incompatible class (base name)\\: {0} but expected {1}\r\n\r\n");
\ No newline at end of file
diff --git a/src/org/apache/harmony/luni/util/Msg.java b/src/org/apache/harmony/luni/util/Msg.java
new file mode 100644 (file)
index 0000000..d90ee77
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.harmony.luni.util;
+
+/**
+ * This class retrieves strings from a resource bundle and returns them,
+ * formatting them with MessageFormat when required.
+ * <p>
+ * It is used by the system classes to provide national language support, by
+ * looking up messages in the <code>
+ *    org.apache.harmony.luni.util.ExternalMessages
+ * </code>
+ * resource bundle. Note that if this file is not available, or an invalid key
+ * is looked up, or resource bundle support is not available, the key itself
+ * will be returned as the associated message. This means that the <em>KEY</em>
+ * should a reasonable human-readable (english) string.
+ * 
+ */
+public class Msg {
+
+       final static String msgs = "K0006=Negative index specified\r\nK0007=attempt to write after finish\r\nK0008=Cannot read version\r\nK0009=Missing version string\\: {0}\r\nK000a=Entry is not named\r\nK000b=Invalid attribute {0}\r\nK000c=cannot resolve subclasses\r\nK000d=Unknown attribute\r\nK000e=Cannot add attributes to empty string\r\nK0014=Unquoted {0} in suffix\\: {1}\r\nK0015=Unexpected {0} in fraction\\: {1}\r\nK0016=Unexpected {0} in {1}\r\nK0017=Missing pattern before {0} in {1}\r\nK0018=Missing exponent format {0}\r\nK0019=Unterminated quote {0}\r\nK001a=Missing grouping format {0}\r\nK001b=Invalid exponent format {0}\r\nK001c=Invalid pattern char {0} in {1}\r\nK001d=Invalid argument number\r\nK001e=Missing element format\r\nK001f=Unknown element format\r\nK0020=Unknown format\r\nK002b=Unknown pattern character - \'{0}\'\r\nK002c=Access denied {0}\r\nK002e=Offset out of bounds\r\nK002f=Arguments out of bounds\r\nK0032=Address null or destination port out of range\r\nK0033=Unknown socket type\r\nK0034=Packet address mismatch with connected address\r\nK0035=Zero or negative buffer size\r\nK0036=Invalid negative timeout\r\nK0037=Connection already established\r\nK0038=No host name provided\r\nK0039=Attempted to join a non-multicast group\r\nK003a=Attempted to leave a non-multicast group\r\nK003c=TimeToLive out of bounds\r\nK003d=Socket is closed\r\nK003e=SOCKS connection failed\\: {0}\r\nK003f=Unable to connect to SOCKS server\\: {0}\r\nK0040=Invalid SOCKS client.\r\nK0041=Socket implementation does not support SOCKS.\r\nK0042=Socket implementation factory already set\r\nK0044=The factory has already been set\r\nK0045=Attempted to set a negative SoLinger\r\nK0046=Local port declared out of range\r\nK0047=buffer is null\r\nK0048=Invalid action specified\\: {0}\r\nK0049=MinPort is greater than MaxPort\r\nK004a=Invalid port number specified\r\nK004b=Attempt to set factory more than once.\r\nK004c=Package is sealed\r\nK004d=Does not support writing to the input stream\r\nK004e=Duplicate Factory\r\nK004f=rounding necessary\r\nK0050=wrong rounding mode\r\nK0051=scale value < than zero\r\nK0052=Array index out of range\\: {0}\r\nK0053=Package {0} already defined.\r\nK0055=String index out of range\\: {0}\r\nK0056=Already destroyed\r\nK0057=Has threads\r\nK0058=size must be > 0\r\nK0059=Stream is closed\r\nK005a=Mark has been invalidated.\r\nK005b=BufferedReader is closed\r\nK005c=Invalid Mark.\r\nK005d=Writer is closed.\r\nK005e=size must be >\\= 0\r\nK005f=Does not support writing to the output stream\r\nK0060=CharArrayReader is closed.\r\nK0062=Second byte at {0} does not match UTF8 Specification\r\nK0063=Third byte at {0} does not match UTF8 Specification\r\nK0064=Second or third byte at {0} does not match UTF8 Specification\r\nK0065=Input at {0} does not match UTF8 Specification\r\nK0066=Entry already exists: {0}\r\nK0068=String is too long\r\nK0069=File cannot compare to non File\r\nK006a=time must be positive\r\nK006b=Prefix must be at least 3 characters\r\nK006c=FileDescriptor is null\r\nK006d=actions invalid\r\nK006e=path is null\r\nK006f=invalid permission\\: {0}\r\nK0070=InputStreamReader is closed.\r\nK0071=Error fetching SUID\\: {0}\r\nK0072={0} computing SHA-1 / SUID\r\nK0073=OutputStreamWriter is closed.\r\nK0074=Not connected\r\nK0075=InputStream is closed\r\nK0076=Pipe broken\r\nK0077=Crc mismatch\r\nK0078=Pipe is closed\r\nK0079=Already connected\r\nK007a=Pipe already connected\r\nK007b=Pipe Not Connected\r\nK007e=Pushback buffer full\r\nK007f=Mark/Reset not supported\r\nK0080=Reader is closed\r\nK0081=Mode must be one of \"r\" or \"rw\"\r\nK0083=StringReader is closed.\r\nK0084=can only instantiate one BootstrapClassLoader\r\nK0086=Referenced reflect object is no longer valid\r\nK0087=Referenced reflect object is no longer valid\\: {0}\r\nK0088=Incorrect end of BER tag\r\nK0089=Unknown type\\: {0}\r\nK008a=Read {0} bytes trying to read {1} bytes from {2}\r\nK008b=Position\\: {0}\r\nK008c=Invalid Base64 char\\:{0}\r\nK008d=This protocol does not support input\r\nK008e=Does not support output\r\nK008f=This method does not support writing\\: {0}\r\nK0090=can\'t open OutputStream after reading from an inputStream\r\nK0091=Cannot access request header fields after connection is set\r\nK0092=Cannot set method after connection is made\r\nK0093=Too many redirects\r\nK0094=Unable to change directories\r\nK0095=Could not establish data connection\r\nK0096=Unable to retrieve file\\: {0}\r\nK0097=Unable to connect to server\\: {0}\r\nK0098=Unable to log into server\\: {0}\r\nK0099=Unable to configure data port\r\nK009a=Unable to store file\r\nK009b=Unable to set transfer type\r\nK00a2=Parsing policy file\\: {0}, expected quoted {1}, found unquoted\\: {2}\r\nK00a3=Parsing policy file\\: {0}, found unexpected\\: {1}\r\nK00a4=Content-Length underflow\r\nK00a5=Invalid parameter - {0}\r\nK00a8=Parsing policy file\\: {0}, invalid codesource URL\\: {1}\r\nK00ab=No active entry\r\nK00ae=Size mismatch\r\nK00af=Invalid proxy port\\: {0}\r\nK00b0=Proxy port out of range\r\nK00b1=Invalid port number\r\nK00b2=Content-Length exceeded\r\nK00b3=Unknown protocol\\: {0}\r\nK00b6=No entries\r\nK00b7=File is closed\r\nK00c1=Illegal character\r\nK00cd=Failure to connect to SOCKS server.\r\nK00ce=Unable to connect to identd to verify user.\r\nK00cf=Failure - user ids do not match.\r\nK00d0=Success\r\nK00d1=Read null attempting to read class descriptor for an object\r\nK00d2=Wrong format\\: 0x{0}\r\nK00d3=Read an exception\r\nK00d4={2} - {0} not compatible with {1}\r\nK00d5=Invalid typecode\\: {0}\r\nK00d7=Wrong base type in\\: {0}\r\nK00d8=Protocol not found\\: {0}\r\nK00d9=Callback object cannot be null\r\nK00da=Incompatible class (SUID)\\: {0} but expected {1}\r\nK00dc=IllegalAccessException\r\nK00e3=Could not create specified security manager\\: {0}\r\nK00e4=Key usage is critical and cannot be used for digital signature purposes.\r\nK00e5=month\\: {0}\r\nK00e6=day of month\\: {0}\r\nK00e7=day of week\\: {0}\r\nK00e8=time\\: {0}\r\nK00e9=DST offset\\: {0}\r\nK00ea=era\\: {0}\r\nK00eb={0} failed verification of {1}\r\nK00ec={0} has invalid digest for {1} in {2}\r\nK00ed={0} is not an interface\r\nK00ee={0} is not visible from class loader\r\nK00ef={0} appears more than once\r\nK00f0=non-public interfaces must be in the same package\r\nK00f1=not a proxy instance\r\nK00f2=the methods named {0} must have the same return type\r\nK00f3=Timer was cancelled\r\nK00f5=Illegal delay to start the TimerTask\r\nK00f6=TimerTask is scheduled already\r\nK00f7=TimerTask is cancelled\r\nK00f8=day of week in month\\: {0}\r\nK00f9=min or max digit count too large\r\nK00fa=min digits greater than max digits\r\nK00fb=min or max digits negative\r\nK00fc=Jar entry not specified\r\nK00fd=Invalid keystore\r\nK00fe=Incorrect password\r\nK0185=The alias already exists for a key entry.\r\nK018f=Can\'t convert to BMPString \\: {0}\r\nK0190=No data to decode\r\nK0191=Invalid size, must be a multiple of 64 from 512 to 1024\r\nK0193=An identity with this name already exists in this scope\r\nK0194=An identity in the scope has the same public key\r\nK0195=The existing public key and the one contained in the certificate do not match.\r\nK0196=Certificate is missing\r\nK0199=Count out of range\r\nK01a0=End of stream condition\r\nK01a4=Already shutting down\r\nK01a5=Illegal shutdown hook\\: {0}\r\nK01a6=Invalid filter\r\nK01a7=Name too long: {0}\r\nK01b3=Incorrect number of arguments\r\nK01b4=Cannot convert {0} to {1}\r\nK01b6=Cannot find \\!/\r\nK01c1=File is a Directory\r\nK01c2=Cannot create\\: {0}\r\nK01c3=Unable to open\\: {0}\r\nK01c4=Invalid zip file\\: {0}\r\nK01c6=No Main-Class specified in manifest\\: {0}\r\nK01d1=Signers of \'{0}\' do not match signers of other classes in package\r\nK01d2={1} - protected system package \'{0}\'\r\nK01ec=key size must be a multiple of 8 bits\r\nK01ed=key size must be at least 512 bits\r\nK01fe=Incomplete % sequence at\\: {0}\r\nK01ff=Invalid % sequence ({0}) at\\: {1}\r\nK0220=UTFDataFormatException\r\nK0222=No Manifest found in jar file\\: {0}\r\nK0300=Unsupported encoding\r\nK0301=Not signed data\r\nK0302=Relative path\r\nK0303=Scheme-specific part expected\r\nK0304=Authority expected\r\nK0305=Illegal character in scheme\r\nK0306={0} in schemeSpecificPart\r\nK0307={0} in authority\r\nK0308={0} in path\r\nK0309={0} in query\r\nK030a={0} in fragment\r\nK030c=Expected host\r\nK030d=Illegal character in userinfo\r\nK030e=Expected a closing square bracket for ipv6 address\r\nK030f=Malformed ipv6 address\r\nK0310=Illegal character in host name\r\nK0311=Malformed ipv4 address\r\nK0312=URI is not absolute\r\nK0313=Incomplete % sequence\r\nK0314=Invalid % sequence ({0})\r\nK0315=Socket is already bound\r\nK0316=SocketAddress {0} not supported\r\nK0317=Host is unresolved\\: {0}\r\nK0318=SocketAddress is null\r\nK0319=Exception in thread \"{0}\"\\ \r\nK031a=URI is not absolute\\: {0}\r\nK031b=URI is not hierarchical\\: {0}\r\nK031c=Expected file scheme in URI\\: {0}\r\nK031d=Expected non-empty path in URI\\: {0}\r\nK031e=Found {0} component in URI\\: {1}\r\nK031f=Socket is not bound\r\nK0320=Socket is not connected\r\nK0321=Socket input is shutdown\r\nK0322=Not a supported ISO 4217 Currency Code\\: {0}\r\nK0323=Not a supported ISO 3166 Country locale\\: {0}\r\nK0324=Needs dictionary\r\nK0325=Port out of range\\: {0}\r\nK0326={0} at index {1}\\: {2}\r\nK0327={0}\\: {1}\r\nK0328=Certificate not yet valid\r\nK0329=Certificate expired\r\nK0330=interface name is null\r\nK0331=address is null\r\nK0332=Invalid IP Address is neither 4 or 16 bytes\\: {0}\r\nK0333=Urgent data not supported\r\nK0334=Cannot set network interface with null\r\nK0335=No addresses associated with Interface\r\nK0337=null type not allowed\r\nK0338=Address not associated with an interface - not set\r\nK0339=Invalid IP Address is neither 4 or 16 bytes\r\nK0340={0} incompatible with {1}\r\nK0342=Scheme expected\r\nK0344=Not a valid {0}, subclass should override readResolve()\r\nK0346=Unmatched braces in the pattern\r\nK0347=seek position is negative\r\nK0348=Format specifier \'{0}\'\r\nK0349=Conversion is \'{0}\'\r\nK034a=The flags are {0}\r\nK034b=url and proxy can not be null\r\nK034c=proxy should not be null\r\nK034d=method has not been implemented yet\r\nK034e=Build rules empty\r\nK0351=format is null\r\nKA000=Line too long\r\nKA001=Argument must not be null\r\nKA002=Unshared read of back reference\r\nKA003=different mode already set\r\nKA004=Enums may not be cloned\r\nKA005={0} is not an enum type\r\nKA006={0} is not a constant in the enum type {1}\r\nKA007=field is null\r\nKA008={0} is an illegal radix\r\nKA009=CharsetName is illegal\r\nKA00a=File is null\r\nKA00b=InputStream is null\r\nKA00c=Readable is null\r\nKA00d=ReadableByteChannel is null\r\nKA00e=Radix {0} is less than Character.MIN_RADIX or greater than Character.MAX_RADIX\r\nKA00f=Socket output is shutdown\r\nKA010=Cannot read back reference to unshared object\r\nKA011=Malformed reply from SOCKS server\r\nKA012=No such file or directory\r\nKA013=Number of bytes to skip cannot be negative\r\nKA014=Invalit UUID string\r\nKA015=Incompatible class (base name)\\: {0} but expected {1}\r\n\r\n";
+
+       // ResourceBundle holding the system messages.
+//     static private ResourceBundle bundle = null;
+
+//     /**
+//      * @j2sIgnore
+//      */
+//     static {
+//             // Attempt to load the messages.
+//             try {
+//                     bundle = MsgHelp.setLocale(Locale.getDefault(),
+//                                     "org.apache.harmony.luni.util.ExternalMessages");
+//             } catch (Throwable e) {
+//                     e.printStackTrace();
+//             }
+//     }
+
+       /**
+        * Retrieves a message which has no arguments.
+        * 
+        * @param msg
+        *            String the key to look up.
+        * @return String the message for that key in the system message bundle.
+        */
+       static public String getString(String msg) {
+               return getMsg(msg);
+//             if (bundle == null)
+//                     return msg;
+//             try {
+//                     return bundle.getString(msg);
+//             } catch (MissingResourceException e) {
+//                     return msg;
+//             }
+       }
+
+       /**
+        * Retrieves a message which takes 1 argument.
+        * 
+        * @param msg
+        *            String the key to look up.
+        * @param arg
+        *            Object the object to insert in the formatted output.
+        * @return String the message for that key in the system message bundle.
+        */
+       static public String getString(String msg, Object arg) {
+               return getString(msg, new Object[] { arg });
+       }
+
+       /**
+        * Retrieves a message which takes 1 integer argument.
+        * 
+        * @param msg
+        *            String the key to look up.
+        * @param arg
+        *            int the integer to insert in the formatted output.
+        * @return String the message for that key in the system message bundle.
+        */
+       static public String getString(String msg, int arg) {
+               return getString(msg, new Object[] { Integer.toString(arg) });
+       }
+
+       /**
+        * Retrieves a message which takes 1 character argument.
+        * 
+        * @param msg
+        *            String the key to look up.
+        * @param arg
+        *            char the character to insert in the formatted output.
+        * @return String the message for that key in the system message bundle.
+        */
+       static public String getString(String msg, char arg) {
+               return getString(msg, new Object[] { String.valueOf(arg) });
+       }
+
+       /**
+        * Retrieves a message which takes 2 arguments.
+        * 
+        * @param msg
+        *            String the key to look up.
+        * @param arg1
+        *            Object an object to insert in the formatted output.
+        * @param arg2
+        *            Object another object to insert in the formatted output.
+        * @return String the message for that key in the system message bundle.
+        */
+       static public String getString(String msg, Object arg1, Object arg2) {
+               return getString(msg, new Object[] { arg1, arg2 });
+       }
+
+       /**
+        * Retrieves a message which takes several arguments.
+        * 
+        * @param msg
+        *            String the key to look up.
+        * @param args
+        *            Object[] the objects to insert in the formatted output.
+        * @return String the message for that key in the system message bundle.
+        */
+       static public String getString(String msg, Object[] args) {
+               String format = getMsg(msg);
+//             
+//
+//             if (bundle != null) {
+//                     try {
+//                             format = bundle.getString(msg);
+//                     } catch (MissingResourceException e) {
+//                     }
+//             }
+//
+               return format(format, args);
+       }
+
+       /**
+        * SwingJS minimal support for harmony error messages
+        * 
+        * @param msg
+        * @return
+        */
+       private static String getMsg(String msg) {
+               int pt = msgs.indexOf(msg);
+               return (pt < 0 ? msg : msg.substring(pt + 6, msg.indexOf("\r", pt)));
+       }
+       
+       // from MsgHelp.java
+       
+       /**
+        * Generates a formatted text string given a source string containing
+        * "argument markers" of the form "{argNum}" where each argNum must be in
+        * the range 0..9. The result is generated by inserting the toString of each
+        * argument into the position indicated in the string.
+        * <p>
+        * To insert the "{" character into the output, use a single backslash
+        * character to escape it (i.e. "\{"). The "}" character does not need to be
+        * escaped.
+        * 
+        * @param format
+        *            String the format to use when printing.
+        * @param args
+        *            Object[] the arguments to use.
+        * @return String the formatted message.
+        */
+       public static String format(String format, Object[] args) {
+               StringBuilder answer = new StringBuilder(format.length()
+                               + (args.length * 20));
+               String[] argStrings = new String[args.length];
+               for (int i = 0; i < args.length; ++i) {
+                       if (args[i] == null)
+                               argStrings[i] = "<null>";
+                       else
+                               argStrings[i] = args[i].toString();
+               }
+               int lastI = 0;
+               for (int i = format.indexOf('{', 0); i >= 0; i = format.indexOf('{',
+                               lastI)) {
+                       if (i != 0 && format.charAt(i - 1) == '\\') {
+                               // It's escaped, just print and loop.
+                               if (i != 1)
+                                       answer.append(format.substring(lastI, i - 1));
+                               answer.append('{');
+                               lastI = i + 1;
+                       } else {
+                               // It's a format character.
+                               if (i > format.length() - 3) {
+                                       // Bad format, just print and loop.
+                                       answer.append(format.substring(lastI, format.length()));
+                                       lastI = format.length();
+                               } else {
+//                                     int argnum = (byte) Character.digit(format.charAt(i + 1),
+//                                                     10);
+                                       int argnum = (byte) (format.charAt(i + 1) - '0');
+                                       if (argnum < 0 || format.charAt(i + 2) != '}') {
+                                               // Bad format, just print and loop.
+                                               answer.append(format.substring(lastI, i + 1));
+                                               lastI = i + 1;
+                                       } else {
+                                               // Got a good one!
+                                               answer.append(format.substring(lastI, i));
+                                               if (argnum >= argStrings.length)
+                                                       answer.append("<missing argument>");
+                                               else
+                                                       answer.append(argStrings[argnum]);
+                                               lastI = i + 3;
+                                       }
+                               }
+                       }
+               }
+               if (lastI < format.length())
+                       answer.append(format.substring(lastI, format.length()));
+               return answer.toString();
+       }
+
+}
diff --git a/src/org/apache/harmony/luni/util/MsgHelp.java b/src/org/apache/harmony/luni/util/MsgHelp.java
new file mode 100644 (file)
index 0000000..592d8b3
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.harmony.luni.util;
+
+/**
+ * This class contains helper methods for loading resource bundles and
+ * formatting external message strings.
+ * 
+ */
+
+public final class MsgHelp {
+
+       /**
+        * Generates a formatted text string given a source string containing
+        * "argument markers" of the form "{argNum}" where each argNum must be in
+        * the range 0..9. The result is generated by inserting the toString of each
+        * argument into the position indicated in the string.
+        * <p>
+        * To insert the "{" character into the output, use a single backslash
+        * character to escape it (i.e. "\{"). The "}" character does not need to be
+        * escaped.
+        * 
+        * @param format
+        *            String the format to use when printing.
+        * @param args
+        *            Object[] the arguments to use.
+        * @return String the formatted message.
+        */
+       public static String format(String format, Object[] args) {
+               StringBuilder answer = new StringBuilder(format.length()
+                               + (args.length * 20));
+               String[] argStrings = new String[args.length];
+               for (int i = 0; i < args.length; ++i) {
+                       if (args[i] == null)
+                               argStrings[i] = "<null>";
+                       else
+                               argStrings[i] = args[i].toString();
+               }
+               int lastI = 0;
+               for (int i = format.indexOf('{', 0); i >= 0; i = format.indexOf('{',
+                               lastI)) {
+                       if (i != 0 && format.charAt(i - 1) == '\\') {
+                               // It's escaped, just print and loop.
+                               if (i != 1)
+                                       answer.append(format.substring(lastI, i - 1));
+                               answer.append('{');
+                               lastI = i + 1;
+                       } else {
+                               // It's a format character.
+                               if (i > format.length() - 3) {
+                                       // Bad format, just print and loop.
+                                       answer.append(format.substring(lastI, format.length()));
+                                       lastI = format.length();
+                               } else {
+//                                     int argnum = (byte) Character.digit(format.charAt(i + 1),
+//                                                     10);
+                                       int argnum = (byte) (format.charAt(i + 1) - '0');
+                                       if (argnum < 0 || format.charAt(i + 2) != '}') {
+                                               // Bad format, just print and loop.
+                                               answer.append(format.substring(lastI, i + 1));
+                                               lastI = i + 1;
+                                       } else {
+                                               // Got a good one!
+                                               answer.append(format.substring(lastI, i));
+                                               if (argnum >= argStrings.length)
+                                                       answer.append("<missing argument>");
+                                               else
+                                                       answer.append(argStrings[argnum]);
+                                               lastI = i + 3;
+                                       }
+                               }
+                       }
+               }
+               if (lastI < format.length())
+                       answer.append(format.substring(lastI, format.length()));
+               return answer.toString();
+       }
+
+//     /**
+//      * Changes the locale of the messages.
+//      * 
+//      * @param locale
+//      *            Locale the locale to change to.
+//      */
+//     static public ResourceBundle setLocale(final Locale locale,
+//                     final String resource) {
+//             /*
+//             try {
+//                     final ClassLoader loader = VM.bootCallerClassLoader();
+//                     return (ResourceBundle) AccessController
+//                                     .doPrivileged(new PrivilegedAction<Object>() {
+//                                             public Object run() {
+//                                                     return ResourceBundle.getBundle(resource, locale,
+//                                                                     loader != null ? loader : ClassLoader.getSystemClassLoader());
+//                                             }
+//                                     });
+//             } catch (MissingResourceException e) {
+//             }
+//             */
+//             /**
+//              * @j2sNative
+//              * 
+//              * Class.forName("java.util.ResourceBundle");
+//              */
+//             {}
+//             return ResourceBundle.getBundle(resource);
+//             //return null;
+//     }
+}
diff --git a/src/org/apache/tools/bzip2/BZip2Constants.java b/src/org/apache/tools/bzip2/BZip2Constants.java
new file mode 100644 (file)
index 0000000..3a511a7
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron@aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+
+package org.apache.tools.bzip2;
+
+/**
+ * Base class for both the compress and decompress classes.
+ * Holds common arrays, and static data.
+ * <p>
+ * This interface is public for historical purposes.
+ * You should have no need to use it.
+ * </p>
+ */
+public interface BZip2Constants {
+
+    int baseBlockSize = 100000;
+    int MAX_ALPHA_SIZE = 258;
+    int MAX_CODE_LEN = 23;
+    int RUNA = 0;
+    int RUNB = 1;
+    int N_GROUPS = 6;
+    int G_SIZE = 50;
+    int N_ITERS = 4;
+    int MAX_SELECTORS = (2 + (900000 / G_SIZE));
+    int NUM_OVERSHOOT_BYTES = 20;
+
+    /**
+     * This array really shouldn't be here.
+     * Again, for historical purposes it is.
+     *
+     * <p>FIXME: This array should be in a private or package private
+     * location, since it could be modified by malicious code.</p>
+     */
+    int[] rNums = {
+        619, 720, 127, 481, 931, 816, 813, 233, 566, 247,
+        985, 724, 205, 454, 863, 491, 741, 242, 949, 214,
+        733, 859, 335, 708, 621, 574, 73, 654, 730, 472,
+        419, 436, 278, 496, 867, 210, 399, 680, 480, 51,
+        878, 465, 811, 169, 869, 675, 611, 697, 867, 561,
+        862, 687, 507, 283, 482, 129, 807, 591, 733, 623,
+        150, 238, 59, 379, 684, 877, 625, 169, 643, 105,
+        170, 607, 520, 932, 727, 476, 693, 425, 174, 647,
+        73, 122, 335, 530, 442, 853, 695, 249, 445, 515,
+        909, 545, 703, 919, 874, 474, 882, 500, 594, 612,
+        641, 801, 220, 162, 819, 984, 589, 513, 495, 799,
+        161, 604, 958, 533, 221, 400, 386, 867, 600, 782,
+        382, 596, 414, 171, 516, 375, 682, 485, 911, 276,
+        98, 553, 163, 354, 666, 933, 424, 341, 533, 870,
+        227, 730, 475, 186, 263, 647, 537, 686, 600, 224,
+        469, 68, 770, 919, 190, 373, 294, 822, 808, 206,
+        184, 943, 795, 384, 383, 461, 404, 758, 839, 887,
+        715, 67, 618, 276, 204, 918, 873, 777, 604, 560,
+        951, 160, 578, 722, 79, 804, 96, 409, 713, 940,
+        652, 934, 970, 447, 318, 353, 859, 672, 112, 785,
+        645, 863, 803, 350, 139, 93, 354, 99, 820, 908,
+        609, 772, 154, 274, 580, 184, 79, 626, 630, 742,
+        653, 282, 762, 623, 680, 81, 927, 626, 789, 125,
+        411, 521, 938, 300, 821, 78, 343, 175, 128, 250,
+        170, 774, 972, 275, 999, 639, 495, 78, 352, 126,
+        857, 956, 358, 619, 580, 124, 737, 594, 701, 612,
+        669, 112, 134, 694, 363, 992, 809, 743, 168, 974,
+        944, 375, 748, 52, 600, 747, 642, 182, 862, 81,
+        344, 805, 988, 739, 511, 655, 814, 334, 249, 515,
+        897, 955, 664, 981, 649, 113, 974, 459, 893, 228,
+        433, 837, 553, 268, 926, 240, 102, 654, 459, 51,
+        686, 754, 806, 760, 493, 403, 415, 394, 687, 700,
+        946, 670, 656, 610, 738, 392, 760, 799, 887, 653,
+        978, 321, 576, 617, 626, 502, 894, 679, 243, 440,
+        680, 879, 194, 572, 640, 724, 926, 56, 204, 700,
+        707, 151, 457, 449, 797, 195, 791, 558, 945, 679,
+        297, 59, 87, 824, 713, 663, 412, 693, 342, 606,
+        134, 108, 571, 364, 631, 212, 174, 643, 304, 329,
+        343, 97, 430, 751, 497, 314, 983, 374, 822, 928,
+        140, 206, 73, 263, 980, 736, 876, 478, 430, 305,
+        170, 514, 364, 692, 829, 82, 855, 953, 676, 246,
+        369, 970, 294, 750, 807, 827, 150, 790, 288, 923,
+        804, 378, 215, 828, 592, 281, 565, 555, 710, 82,
+        896, 831, 547, 261, 524, 462, 293, 465, 502, 56,
+        661, 821, 976, 991, 658, 869, 905, 758, 745, 193,
+        768, 550, 608, 933, 378, 286, 215, 979, 792, 961,
+        61, 688, 793, 644, 986, 403, 106, 366, 905, 644,
+        372, 567, 466, 434, 645, 210, 389, 550, 919, 135,
+        780, 773, 635, 389, 707, 100, 626, 958, 165, 504,
+        920, 176, 193, 713, 857, 265, 203, 50, 668, 108,
+        645, 990, 626, 197, 510, 357, 358, 850, 858, 364,
+        936, 638
+    };
+}
diff --git a/src/org/apache/tools/bzip2/CBZip2InputStream.java b/src/org/apache/tools/bzip2/CBZip2InputStream.java
new file mode 100644 (file)
index 0000000..fc25a31
--- /dev/null
@@ -0,0 +1,1063 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron@aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+package org.apache.tools.bzip2;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An input stream that decompresses from the BZip2 format (without the file
+ * header chars) to be read as any other stream.
+ *
+ * <p>The decompression requires large amounts of memory. Thus you
+ * should call the {@link #close() close()} method as soon as
+ * possible, to force <tt>CBZip2InputStream</tt> to release the
+ * allocated memory.  See {@link CBZip2OutputStream
+ * CBZip2OutputStream} for information about memory usage.</p>
+ *
+ * <p><tt>CBZip2InputStream</tt> reads bytes from the compressed
+ * source stream via the single byte {@link java.io.InputStream#read()
+ * read()} method exclusively. Thus you should consider to use a
+ * buffered source stream.</p>
+ * 
+ * <p>Instances of this class are not threadsafe.</p>
+ */
+public class CBZip2InputStream extends InputStream implements BZip2Constants {
+
+    /**
+     * Index of the last char in the block, so the block size == last + 1.
+     */
+    private int  last;
+
+    /**
+     * Index in zptr[] of original string after sorting.
+     */
+    private int  origPtr;
+
+    /**
+     * always: in the range 0 .. 9.
+     * The current block size is 100000 * this number.
+     */
+    private int blockSize100k;
+
+    private boolean blockRandomised;
+
+    private int bsBuff;
+    private int bsLive;
+    private final CRC crc = new CRC();
+
+    private int nInUse;
+
+    private InputStream in;
+    private final boolean decompressConcatenated;
+
+    private int currentChar = -1;
+
+    private static final int EOF                  = 0;
+    private static final int START_BLOCK_STATE = 1;
+    private static final int RAND_PART_A_STATE = 2;
+    private static final int RAND_PART_B_STATE = 3;
+    private static final int RAND_PART_C_STATE = 4;
+    private static final int NO_RAND_PART_A_STATE = 5;
+    private static final int NO_RAND_PART_B_STATE = 6;
+    private static final int NO_RAND_PART_C_STATE = 7;
+
+    private int currentState = START_BLOCK_STATE;
+
+    private int storedBlockCRC, storedCombinedCRC;
+    private int computedBlockCRC, computedCombinedCRC;
+
+    // Variables used by setup* methods exclusively
+
+    private int su_count;
+    private int su_ch2;
+    private int su_chPrev;
+    private int su_i2;
+    private int su_j2;
+    private int su_rNToGo;
+    private int su_rTPos;
+    private int su_tPos;
+    private char su_z;
+
+    /**
+     * All memory intensive stuff.
+     * This field is initialized by initBlock().
+     */
+    private CBZip2InputStream.Data data;
+
+    /**
+     * Constructs a new CBZip2InputStream which decompresses bytes read from
+     * the specified stream. This doesn't suppprt decompressing
+     * concatenated .bz2 files.
+     *
+     * <p>Although BZip2 headers are marked with the magic
+     * <tt>"Bz"</tt> this constructor expects the next byte in the
+     * stream to be the first one after the magic.  Thus callers have
+     * to skip the first two bytes. Otherwise this constructor will
+     * throw an exception. </p>
+     * @param in 
+     *
+     * @throws IOException
+     *  if the stream content is malformed or an I/O error occurs.
+     * @throws NullPointerException
+     *  if <tt>in == null</tt>
+     */
+    public CBZip2InputStream(final InputStream in) throws IOException {
+        this(in, false);
+    }
+
+    /**
+     * Constructs a new CBZip2InputStream which decompresses bytes
+     * read from the specified stream.
+     *
+     * <p>Although BZip2 headers are marked with the magic
+     * <tt>"Bz"</tt> this constructor expects the next byte in the
+     * stream to be the first one after the magic.  Thus callers have
+     * to skip the first two bytes. Otherwise this constructor will
+     * throw an exception. </p>
+     *
+     * @param in the InputStream from which this object should be created
+     * @param decompressConcatenated
+     *                     if true, decompress until the end of the input;
+     *                     if false, stop after the first .bz2 stream and
+     *                     leave the input position to point to the next
+     *                     byte after the .bz2 stream
+     *
+     * @throws IOException
+     *             if the stream content is malformed or an I/O error occurs.
+     * @throws NullPointerException
+     *             if <tt>in == null</tt>
+     */
+    public CBZip2InputStream(final InputStream in,
+                             final boolean decompressConcatenated)
+            throws IOException {
+        super();
+
+        this.in = in;
+        this.decompressConcatenated = decompressConcatenated;
+
+        init(true);
+        initBlock();
+        setupBlock();
+    }
+
+  /** {@inheritDoc} */
+  @Override
+  public int read() throws IOException {
+    if (this.in == null)
+      throw new IOException("stream closed");
+    return read0();
+  }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.InputStream#read(byte[], int, int)
+     */
+    @Override
+    public int read(final byte[] dest, final int offs, final int len)
+        throws IOException {
+        if (offs < 0) {
+            throw new IndexOutOfBoundsException("offs(" + offs + ") < 0.");
+        }
+        if (len < 0) {
+            throw new IndexOutOfBoundsException("len(" + len + ") < 0.");
+        }
+        if (offs + len > dest.length) {
+            throw new IndexOutOfBoundsException("offs(" + offs + ") + len("
+                                                + len + ") > dest.length("
+                                                + dest.length + ").");
+        }
+        if (this.in == null) {
+            throw new IOException("stream closed");
+        }
+
+        final int hi = offs + len;
+        int destOffs = offs;
+        for (int b; (destOffs < hi) && ((b = read0()) >= 0);) {
+            dest[destOffs++] = (byte) b;
+        }
+
+        return (destOffs == offs) ? -1 : (destOffs - offs);
+    }
+
+    private void makeMaps() {
+        final boolean[] inUse   = this.data.inUse;
+        final byte[] seqToUnseq = this.data.seqToUnseq;
+
+        int nInUseShadow = 0;
+
+        for (int i = 0; i < 256; i++) {
+            if (inUse[i]) {
+                seqToUnseq[nInUseShadow++] = (byte) i;
+            }
+        }
+
+        this.nInUse = nInUseShadow;
+    }
+
+    private int read0() throws IOException {
+        final int retChar = this.currentChar;
+
+        switch (this.currentState) {
+        case EOF:
+            return -1;
+
+        case START_BLOCK_STATE:
+            throw new IllegalStateException();
+
+        case RAND_PART_A_STATE:
+            throw new IllegalStateException();
+
+        case RAND_PART_B_STATE:
+            setupRandPartB();
+            break;
+
+        case RAND_PART_C_STATE:
+            setupRandPartC();
+            break;
+
+        case NO_RAND_PART_A_STATE:
+            throw new IllegalStateException();
+
+        case NO_RAND_PART_B_STATE:
+            setupNoRandPartB();
+            break;
+
+        case NO_RAND_PART_C_STATE:
+            setupNoRandPartC();
+            break;
+
+        default:
+            throw new IllegalStateException();
+        }
+
+        return retChar;
+    }
+
+    private boolean init(boolean isFirstStream) throws IOException {
+        if (null == in) {
+            throw new IOException("No InputStream");
+        }
+        
+        if (isFirstStream) {
+            if (in.available() == 0) {
+                throw new IOException("Empty InputStream");
+            }
+        } else {
+            int magic0 = read();
+            if (magic0 == -1) {
+                return false;
+            }
+            int magic1 = read();
+            if (magic0 != 'B' || magic1 != 'Z') {
+                throw new IOException("Garbage after a valid BZip2 stream");
+            }
+        }
+
+        int magic2 = read();
+        if (magic2 != 'h') {
+            throw new IOException(isFirstStream
+                    ? "Stream is not in the BZip2 format"
+                    : "Garbage after a valid BZip2 stream");
+        }
+
+        int blockSize = read();
+        if ((blockSize < '1') || (blockSize > '9')) {
+            throw new IOException("Stream is not BZip2 formatted: illegal "
+                                  + "blocksize " + (char) blockSize);
+        }
+
+        this.blockSize100k = blockSize - '0';
+
+        this.bsLive = 0;
+        this.computedCombinedCRC = 0;
+
+        return true;
+    }
+
+  private void initBlock() throws IOException {
+    char magic0;
+    char magic1;
+    char magic2;
+    char magic3;
+    char magic4;
+    char magic5;
+
+    while (true) {
+      // Get the block magic bytes.
+      magic0 = bsGetUByte();
+      magic1 = bsGetUByte();
+      magic2 = bsGetUByte();
+      magic3 = bsGetUByte();
+      magic4 = bsGetUByte();
+      magic5 = bsGetUByte();
+
+      // If isn't end of stream magic, break out of the loop.
+      if (magic0 != 0x17 || magic1 != 0x72 || magic2 != 0x45 || magic3 != 0x38
+          || magic4 != 0x50 || magic5 != 0x90) {
+        break;
+      }
+
+      // End of stream was reached. Check the combined CRC and
+      // advance to the next .bz2 stream if decoding concatenated
+      // streams.
+      if (complete()) {
+        return;
+      }
+    }
+
+    if (magic0 != 0x31 || // '1'
+        magic1 != 0x41 || // ')'
+        magic2 != 0x59 || // 'Y'
+        magic3 != 0x26 || // '&'
+        magic4 != 0x53 || // 'S'
+        magic5 != 0x59 // 'Y'
+    ) {
+      this.currentState = EOF;
+      throw new IOException("bad block header");
+    }
+    this.storedBlockCRC = bsGetInt();
+    this.blockRandomised = bsR(1) == 1;
+
+    /**
+     * Allocate data here instead in constructor, so we do not allocate it if
+     * the input file is empty.
+     */
+    if (this.data == null) {
+      this.data = new Data(this.blockSize100k);
+    }
+
+    // currBlockNo++;
+    getAndMoveToFrontDecode();
+
+    this.crc.initialiseCRC();
+    this.currentState = START_BLOCK_STATE;
+  }
+
+    private void endBlock() throws IOException {
+        this.computedBlockCRC = this.crc.getFinalCRC();
+
+        // A bad CRC is considered a fatal error.
+        if (this.storedBlockCRC != this.computedBlockCRC) {
+            // make next blocks readable without error
+            // (repair feature, not yet documented, not tested)
+            this.computedCombinedCRC
+                = (this.storedCombinedCRC << 1)
+                | (this.storedCombinedCRC >>> 31);
+            this.computedCombinedCRC ^= this.storedBlockCRC;
+
+            reportCRCError();
+        }
+
+        this.computedCombinedCRC
+            = (this.computedCombinedCRC << 1)
+            | (this.computedCombinedCRC >>> 31);
+        this.computedCombinedCRC ^= this.computedBlockCRC;
+    }
+
+    private boolean complete() throws IOException {
+        this.storedCombinedCRC = bsGetInt();
+        this.currentState = EOF;
+        this.data = null;
+
+        if (this.storedCombinedCRC != this.computedCombinedCRC) {
+            reportCRCError();
+        }
+
+        // Look for the next .bz2 stream if decompressing
+        // concatenated files.
+        return !decompressConcatenated || !init(false);
+    }
+
+    @Override
+    public void close() throws IOException {
+        InputStream inShadow = this.in;
+        if (inShadow != null) {
+            try {
+                if (inShadow != System.in) {
+                    inShadow.close();
+                }
+            } finally {
+                this.data = null;
+                this.in = null;
+            }
+        }
+    }
+
+    private int bsR(final int n) throws IOException {
+        int bsLiveShadow = this.bsLive;
+        int bsBuffShadow = this.bsBuff;
+
+        if (bsLiveShadow < n) {
+            final InputStream inShadow = this.in;
+            do {
+                int thech = read();//inShadow.read();
+
+                if (thech < 0) {
+                    throw new IOException("unexpected end of stream");
+                }
+
+                bsBuffShadow = (bsBuffShadow << 8) | thech;
+                bsLiveShadow += 8;
+            } while (bsLiveShadow < n);
+
+            this.bsBuff = bsBuffShadow;
+        }
+
+        this.bsLive = bsLiveShadow - n;
+        return (bsBuffShadow >> (bsLiveShadow - n)) & ((1 << n) - 1);
+    }
+
+    private boolean bsGetBit() throws IOException {
+        int bsLiveShadow = this.bsLive;
+        int bsBuffShadow = this.bsBuff;
+
+        if (bsLiveShadow < 1) {
+            int thech = read();
+
+            if (thech < 0) {
+                throw new IOException("unexpected end of stream");
+            }
+
+            bsBuffShadow = (bsBuffShadow << 8) | thech;
+            bsLiveShadow += 8;
+            this.bsBuff = bsBuffShadow;
+        }
+
+        this.bsLive = bsLiveShadow - 1;
+        return ((bsBuffShadow >> (bsLiveShadow - 1)) & 1) != 0;
+    }
+
+    private char bsGetUByte() throws IOException {
+        return (char) bsR(8);
+    }
+
+    private int bsGetInt() throws IOException {
+        return (((((bsR(8) << 8) | bsR(8)) << 8) | bsR(8)) << 8) | bsR(8);
+    }
+
+    /**
+     * Called by createHuffmanDecodingTables() exclusively.
+     * @param limit 
+     * @param base 
+     * @param perm 
+     * @param length 
+     * @param minLen 
+     * @param maxLen 
+     * @param alphaSize 
+     */
+    private static void hbCreateDecodeTables(final int[] limit,
+                                             final int[] base,
+                                             final int[] perm,
+                                             final char[] length,
+                                             final int minLen,
+                                             final int maxLen,
+                                             final int alphaSize) {
+        for (int i = minLen, pp = 0; i <= maxLen; i++) {
+            for (int j = 0; j < alphaSize; j++) {
+                if (length[j] == i) {
+                    perm[pp++] = j;
+                }
+            }
+        }
+
+        for (int i = MAX_CODE_LEN; --i > 0;) {
+            base[i] = 0;
+            limit[i] = 0;
+        }
+
+        for (int i = 0; i < alphaSize; i++) {
+            base[length[i] + 1]++;
+        }
+
+        for (int i = 1, b = base[0]; i < MAX_CODE_LEN; i++) {
+            b += base[i];
+            base[i] = b;
+        }
+
+        for (int i = minLen, vec = 0, b = base[i]; i <= maxLen; i++) {
+            final int nb = base[i + 1];
+            vec += nb - b;
+            b = nb;
+            limit[i] = vec - 1;
+            vec <<= 1;
+        }
+
+        for (int i = minLen + 1; i <= maxLen; i++) {
+            base[i] = ((limit[i - 1] + 1) << 1) - base[i];
+        }
+    }
+
+    private void recvDecodingTables() throws IOException {
+        final Data dataShadow     = this.data;
+        final boolean[] inUse     = dataShadow.inUse;
+        final byte[] pos          = dataShadow.recvDecodingTables_pos;
+        final byte[] selector     = dataShadow.selector;
+        final byte[] selectorMtf  = dataShadow.selectorMtf;
+
+        int inUse16 = 0;
+
+        /* Receive the mapping table */
+        for (int i = 0; i < 16; i++) {
+            if (bsGetBit()) {
+                inUse16 |= 1 << i;
+            }
+        }
+
+        for (int i = 256; --i >= 0;) {
+            inUse[i] = false;
+        }
+
+        for (int i = 0; i < 16; i++) {
+            if ((inUse16 & (1 << i)) != 0) {
+                final int i16 = i << 4;
+                for (int j = 0; j < 16; j++) {
+                    if (bsGetBit()) {
+                        inUse[i16 + j] = true;
+                    }
+                }
+            }
+        }
+
+        makeMaps();
+        final int alphaSize = this.nInUse + 2;
+
+        /* Now the selectors */
+        final int nGroups = bsR(3);
+        final int nSelectors = bsR(15);
+
+        for (int i = 0; i < nSelectors; i++) {
+            int j = 0;
+            while (bsGetBit()) {
+                j++;
+            }
+            selectorMtf[i] = (byte) j;
+        }
+
+        /* Undo the MTF values for the selectors. */
+        for (int v = nGroups; --v >= 0;) {
+            pos[v] = (byte) v;
+        }
+
+        for (int i = 0; i < nSelectors; i++) {
+            int v = selectorMtf[i] & 0xff;
+            final byte tmp = pos[v];
+            while (v > 0) {
+                // nearly all times v is zero, 4 in most other cases
+                pos[v] = pos[v - 1];
+                v--;
+            }
+            pos[0] = tmp;
+            selector[i] = tmp;
+        }
+
+        final char[][] len  = dataShadow.temp_charArray2d;
+
+        /* Now the coding tables */
+        for (int t = 0; t < nGroups; t++) {
+            int curr = bsR(5);
+            final char[] len_t = len[t];
+            for (int i = 0; i < alphaSize; i++) {
+                while (bsGetBit()) {
+                    curr += bsGetBit() ? -1 : 1;
+                }
+                len_t[i] = (char) curr;
+            }
+        }
+
+        // finally create the Huffman tables
+        createHuffmanDecodingTables(alphaSize, nGroups);
+    }
+
+    /**
+     * Called by recvDecodingTables() exclusively.
+     * @param alphaSize 
+     * @param nGroups 
+     */
+    private void createHuffmanDecodingTables(final int alphaSize,
+                                             final int nGroups) {
+        final Data dataShadow = this.data;
+        final char[][] len  = dataShadow.temp_charArray2d;
+        final int[] minLens = dataShadow.minLens;
+        final int[][] limit = dataShadow.limit;
+        final int[][] base  = dataShadow.base;
+        final int[][] perm  = dataShadow.perm;
+
+        for (int t = 0; t < nGroups; t++) {
+            int minLen = 32;
+            int maxLen = 0;
+            final char[] len_t = len[t];
+            for (int i = alphaSize; --i >= 0;) {
+                final char lent = len_t[i];
+                if (lent > maxLen) {
+                    maxLen = lent;
+                }
+                if (lent < minLen) {
+                    minLen = lent;
+                }
+            }
+            hbCreateDecodeTables(limit[t], base[t], perm[t], len[t], minLen,
+                                 maxLen, alphaSize);
+            minLens[t] = minLen;
+        }
+    }
+
+  private void getAndMoveToFrontDecode() throws IOException {
+    this.origPtr = bsR(24);
+    recvDecodingTables();
+
+    final InputStream inShadow = this.in;
+    final Data dataShadow = this.data;
+    final byte[] ll8 = dataShadow.ll8;
+    final int[] unzftab = dataShadow.unzftab;
+    final byte[] selector = dataShadow.selector;
+    final byte[] seqToUnseq = dataShadow.seqToUnseq;
+    final char[] yy = dataShadow.getAndMoveToFrontDecode_yy;
+    final int[] minLens = dataShadow.minLens;
+    final int[][] limit = dataShadow.limit;
+    final int[][] base = dataShadow.base;
+    final int[][] perm = dataShadow.perm;
+    final int limitLast = this.blockSize100k * 100000;
+
+    /*
+      Setting up the unzftab entries here is not strictly
+      necessary, but it does save having to do it later
+      in a separate pass, and so saves a block's worth of
+      cache misses.
+    */
+    for (int i = 256; --i >= 0;) {
+      yy[i] = (char) i;
+      unzftab[i] = 0;
+    }
+
+    int groupNo = 0;
+    int groupPos = G_SIZE - 1;
+    final int eob = this.nInUse + 1;
+    int nextSym = getAndMoveToFrontDecode0(0);
+    int bsBuffShadow = this.bsBuff;
+    int bsLiveShadow = this.bsLive;
+    int lastShadow = -1;
+    int zt = selector[groupNo] & 0xff;
+    int[] base_zt = base[zt];
+    int[] limit_zt = limit[zt];
+    int[] perm_zt = perm[zt];
+    int minLens_zt = minLens[zt];
+
+    while (nextSym != eob) {
+      if ((nextSym == RUNA) || (nextSym == RUNB)) {
+        int s = -1;
+
+        for (int n = 1; true; n <<= 1) {
+          if (nextSym == RUNA) {
+            s += n;
+          } else if (nextSym == RUNB) {
+            s += n << 1;
+          } else {
+            break;
+          }
+
+          if (groupPos == 0) {
+            groupPos = G_SIZE - 1;
+            zt = selector[++groupNo] & 0xff;
+            base_zt = base[zt];
+            limit_zt = limit[zt];
+            perm_zt = perm[zt];
+            minLens_zt = minLens[zt];
+          } else {
+            groupPos--;
+          }
+
+          int zn = minLens_zt;
+
+          // Inlined:
+          // int zvec = bsR(zn);
+          while (bsLiveShadow < zn) {
+            final int thech = read();//inShadow.read();
+            if (thech < 0)
+              throw new IOException("unexpected end of stream");
+
+            bsBuffShadow = (bsBuffShadow << 8) | thech;
+            bsLiveShadow += 8;
+            continue;
+          }
+          int zvec = (bsBuffShadow >> (bsLiveShadow - zn)) & ((1 << zn) - 1);
+          bsLiveShadow -= zn;
+
+          while (zvec > limit_zt[zn]) {
+            zn++;
+            while (bsLiveShadow < 1) {
+              final int thech = read();//inShadow.read();
+              if (thech < 0)
+                throw new IOException("unexpected end of stream");
+              bsBuffShadow = (bsBuffShadow << 8) | thech;
+              bsLiveShadow += 8;
+              continue;
+            }
+            bsLiveShadow--;
+            zvec = (zvec << 1) | ((bsBuffShadow >> bsLiveShadow) & 1);
+          }
+          nextSym = perm_zt[zvec - base_zt[zn]];
+        }
+
+        final byte ch = seqToUnseq[yy[0]];
+        unzftab[ch & 0xff] += s + 1;
+
+        while (s-- >= 0) {
+          ll8[++lastShadow] = ch;
+        }
+
+        if (lastShadow >= limitLast) {
+          throw new IOException("block overrun");
+        }
+      } else {
+        if (++lastShadow >= limitLast) {
+          throw new IOException("block overrun");
+        }
+
+        final char tmp = yy[nextSym - 1];
+        unzftab[seqToUnseq[tmp] & 0xff]++;
+        ll8[lastShadow] = seqToUnseq[tmp];
+
+        /*
+          This loop is hammered during decompression,
+          hence avoid native method call overhead of
+          System.arraycopy for very small ranges to copy.
+        */
+        if (nextSym <= 16) {
+          for (int j = nextSym - 1; j > 0;) {
+            yy[j] = yy[--j];
+          }
+        } else {
+          System.arraycopy(yy, 0, yy, 1, nextSym - 1);
+        }
+
+        yy[0] = tmp;
+
+        if (groupPos == 0) {
+          groupPos = G_SIZE - 1;
+          zt = selector[++groupNo] & 0xff;
+          base_zt = base[zt];
+          limit_zt = limit[zt];
+          perm_zt = perm[zt];
+          minLens_zt = minLens[zt];
+        } else {
+          groupPos--;
+        }
+
+        int zn = minLens_zt;
+
+        // Inlined:
+        // int zvec = bsR(zn);
+        while (bsLiveShadow < zn) {
+          final int thech = read();//inShadow.read();
+          if (thech < 0)
+            throw new IOException("unexpected end of stream");
+          bsBuffShadow = (bsBuffShadow << 8) | thech;
+          bsLiveShadow += 8;
+          continue;
+        }
+        int zvec = (bsBuffShadow >> (bsLiveShadow - zn)) & ((1 << zn) - 1);
+        bsLiveShadow -= zn;
+
+        while (zvec > limit_zt[zn]) {
+          zn++;
+          while (bsLiveShadow < 1) {
+            final int thech = read();//inShadow.read();
+            if (thech <0) 
+              throw new IOException("unexpected end of stream");
+              bsBuffShadow = (bsBuffShadow << 8) | thech;
+              bsLiveShadow += 8;
+              continue;
+          }
+          bsLiveShadow--;
+          zvec = (zvec << 1) | ((bsBuffShadow >> bsLiveShadow) & 1);
+        }
+        nextSym = perm_zt[zvec - base_zt[zn]];
+      }
+    }
+
+    this.last = lastShadow;
+    this.bsLive = bsLiveShadow;
+    this.bsBuff = bsBuffShadow;
+  }
+
+  private int getAndMoveToFrontDecode0(final int groupNo) throws IOException {
+    final InputStream inShadow = this.in;
+    final Data dataShadow = this.data;
+    final int zt = dataShadow.selector[groupNo] & 0xff;
+    final int[] limit_zt = dataShadow.limit[zt];
+    int zn = dataShadow.minLens[zt];
+    int zvec = bsR(zn);
+    int bsLiveShadow = this.bsLive;
+    int bsBuffShadow = this.bsBuff;
+
+    while (zvec > limit_zt[zn]) {
+      zn++;
+      while (bsLiveShadow < 1) {
+        final int thech = read();//inShadow.read();
+
+        if (thech < 0)
+          throw new IOException("unexpected end of stream");
+
+        bsBuffShadow = (bsBuffShadow << 8) | thech;
+        bsLiveShadow += 8;
+        continue;
+      }
+      bsLiveShadow--;
+      zvec = (zvec << 1) | ((bsBuffShadow >> bsLiveShadow) & 1);
+    }
+
+    this.bsLive = bsLiveShadow;
+    this.bsBuff = bsBuffShadow;
+
+    return dataShadow.perm[zt][zvec - dataShadow.base[zt][zn]];
+  }
+
+    private void setupBlock() throws IOException {
+        if (this.data == null) {
+            return;
+        }
+
+        final int[] cftab = this.data.cftab;
+        final int[] tt    = this.data.initTT(this.last + 1);
+        final byte[] ll8  = this.data.ll8;
+        cftab[0] = 0;
+        System.arraycopy(this.data.unzftab, 0, cftab, 1, 256);
+
+        for (int i = 1, c = cftab[0]; i <= 256; i++) {
+            c += cftab[i];
+            cftab[i] = c;
+        }
+
+        for (int i = 0, lastShadow = this.last; i <= lastShadow; i++) {
+            tt[cftab[ll8[i] & 0xff]++] = i;
+        }
+
+        if ((this.origPtr < 0) || (this.origPtr >= tt.length)) {
+            throw new IOException("stream corrupted");
+        }
+
+        this.su_tPos = tt[this.origPtr];
+        this.su_count = 0;
+        this.su_i2 = 0;
+        this.su_ch2 = 256;   /* not a char and not EOF */
+
+        if (this.blockRandomised) {
+            this.su_rNToGo = 0;
+            this.su_rTPos = 0;
+            setupRandPartA();
+        } else {
+            setupNoRandPartA();
+        }
+    }
+
+    private void setupRandPartA() throws IOException {
+        if (this.su_i2 <= this.last) {
+            this.su_chPrev = this.su_ch2;
+            int su_ch2Shadow = this.data.ll8[this.su_tPos] & 0xff;
+            this.su_tPos = this.data.tt[this.su_tPos];
+            if (this.su_rNToGo == 0) {
+                this.su_rNToGo = BZip2Constants.rNums[this.su_rTPos] - 1;
+                if (++this.su_rTPos == 512) {
+                    this.su_rTPos = 0;
+                }
+            } else {
+                this.su_rNToGo--;
+            }
+            this.su_ch2 = su_ch2Shadow ^= (this.su_rNToGo == 1) ? 1 : 0;
+            this.su_i2++;
+            this.currentChar = su_ch2Shadow;
+            this.currentState = RAND_PART_B_STATE;
+            this.crc.updateCRC(su_ch2Shadow);
+        } else {
+            endBlock();
+            initBlock();
+            setupBlock();
+        }
+    }
+
+    private void setupNoRandPartA() throws IOException {
+        if (this.su_i2 <= this.last) {
+            this.su_chPrev = this.su_ch2;
+            int su_ch2Shadow = this.data.ll8[this.su_tPos] & 0xff;
+            this.su_ch2 = su_ch2Shadow;
+            this.su_tPos = this.data.tt[this.su_tPos];
+            this.su_i2++;
+            this.currentChar = su_ch2Shadow;
+            this.currentState = NO_RAND_PART_B_STATE;
+            this.crc.updateCRC(su_ch2Shadow);
+        } else {
+            this.currentState = NO_RAND_PART_A_STATE;
+            endBlock();
+            initBlock();
+            setupBlock();
+        }
+    }
+
+    private void setupRandPartB() throws IOException {
+        if (this.su_ch2 != this.su_chPrev) {
+            this.currentState = RAND_PART_A_STATE;
+            this.su_count = 1;
+            setupRandPartA();
+        } else if (++this.su_count >= 4) {
+            this.su_z = (char) (this.data.ll8[this.su_tPos] & 0xff);
+            this.su_tPos = this.data.tt[this.su_tPos];
+            if (this.su_rNToGo == 0) {
+                this.su_rNToGo = BZip2Constants.rNums[this.su_rTPos] - 1;
+                if (++this.su_rTPos == 512) {
+                    this.su_rTPos = 0;
+                }
+            } else {
+                this.su_rNToGo--;
+            }
+            this.su_j2 = 0;
+            this.currentState = RAND_PART_C_STATE;
+            if (this.su_rNToGo == 1) {
+                this.su_z ^= 1;
+            }
+            setupRandPartC();
+        } else {
+            this.currentState = RAND_PART_A_STATE;
+            setupRandPartA();
+        }
+    }
+
+    private void setupRandPartC() throws IOException {
+        if (this.su_j2 < this.su_z) {
+            this.currentChar = this.su_ch2;
+            this.crc.updateCRC(this.su_ch2);
+            this.su_j2++;
+        } else {
+            this.currentState = RAND_PART_A_STATE;
+            this.su_i2++;
+            this.su_count = 0;
+            setupRandPartA();
+        }
+    }
+
+    private void setupNoRandPartB() throws IOException {
+        if (this.su_ch2 != this.su_chPrev) {
+            this.su_count = 1;
+            setupNoRandPartA();
+        } else if (++this.su_count >= 4) {
+            this.su_z = (char) (this.data.ll8[this.su_tPos] & 0xff);
+            this.su_tPos = this.data.tt[this.su_tPos];
+            this.su_j2 = 0;
+            setupNoRandPartC();
+        } else {
+            setupNoRandPartA();
+        }
+    }
+
+    private void setupNoRandPartC() throws IOException {
+        if (this.su_j2 < this.su_z) {
+            int su_ch2Shadow = this.su_ch2;
+            this.currentChar = su_ch2Shadow;
+            this.crc.updateCRC(su_ch2Shadow);
+            this.su_j2++;
+            this.currentState = NO_RAND_PART_C_STATE;
+        } else {
+            this.su_i2++;
+            this.su_count = 0;
+            setupNoRandPartA();
+        }
+    }
+
+    private static final class Data extends Object {
+
+        // (with blockSize 900k)
+        final boolean[] inUse   = new boolean[256];                                   //      256 byte
+
+        final byte[] seqToUnseq   = new byte[256];                                    //      256 byte
+        final byte[] selector     = new byte[MAX_SELECTORS];                          //    18002 byte
+        final byte[] selectorMtf  = new byte[MAX_SELECTORS];                          //    18002 byte
+
+        /**
+         * Freq table collected to save a pass over the data during
+         * decompression.
+         */
+        final int[] unzftab = new int[256];                                           //     1024 byte
+
+        final int[][] limit = new int[N_GROUPS][MAX_ALPHA_SIZE];                      //     6192 byte
+        final int[][] base  = new int[N_GROUPS][MAX_ALPHA_SIZE];                      //     6192 byte
+        final int[][] perm  = new int[N_GROUPS][MAX_ALPHA_SIZE];                      //     6192 byte
+        final int[] minLens = new int[N_GROUPS];                                      //       24 byte
+
+        final int[]     cftab     = new int[257];                                     //     1028 byte
+        final char[]    getAndMoveToFrontDecode_yy = new char[256];                   //      512 byte
+        final char[][]  temp_charArray2d  = new char[N_GROUPS][MAX_ALPHA_SIZE];       //     3096 byte
+        final byte[] recvDecodingTables_pos = new byte[N_GROUPS];                     //        6 byte
+        //---------------
+        //    60798 byte
+
+        int[] tt;                                                                     //  3600000 byte
+        byte[] ll8;                                                                   //   900000 byte
+        //---------------
+        //  4560782 byte
+        //===============
+
+        Data(int blockSize100k) {
+            super();
+
+            this.ll8 = new byte[blockSize100k * BZip2Constants.baseBlockSize];
+        }
+
+        /**
+         * Initializes the {@link #tt} array.
+         *
+         * This method is called when the required length of the array
+         * is known.  I don't initialize it at construction time to
+         * avoid unnecessary memory allocation when compressing small
+         * files.
+         * @param length 
+         * @return int array
+         */
+        final int[] initTT(int length) {
+            int[] ttShadow = this.tt;
+
+            // tt.length should always be >= length, but theoretically
+            // it can happen, if the compressor mixed small and large
+            // blocks.  Normally only the last block will be smaller
+            // than others.
+            if ((ttShadow == null) || (ttShadow.length < length)) {
+                this.tt = ttShadow = new int[length];
+            }
+
+            return ttShadow;
+        }
+
+    }
+
+    @SuppressWarnings("unused")
+    private static void reportCRCError() throws IOException {
+        // The clean way would be to throw an exception.
+        //throw new IOException("crc error");
+
+        // Just print a message, like the previous versions of this class did
+        System.err.println("BZip2 CRC error");
+    }
+
+}
+
diff --git a/src/org/apache/tools/bzip2/CBZip2InputStreamFactory.java b/src/org/apache/tools/bzip2/CBZip2InputStreamFactory.java
new file mode 100644 (file)
index 0000000..23923a3
--- /dev/null
@@ -0,0 +1,21 @@
+package org.apache.tools.bzip2;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class CBZip2InputStreamFactory {
+  
+  /**
+   * jsjava addition for reflection
+   * 
+   * @param is
+   * @return BZip2 input stream
+   * @throws IOException 
+   */
+  public CBZip2InputStream getStream(InputStream is) throws IOException {
+    is.read(new byte[2], 0, 2);
+    return new CBZip2InputStream(is);
+  }
+
+}
+
diff --git a/src/org/apache/tools/bzip2/CRC.java b/src/org/apache/tools/bzip2/CRC.java
new file mode 100644 (file)
index 0000000..0102c8e
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron@aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+
+package org.apache.tools.bzip2;
+
+/**
+ * A simple class the hold and calculate the CRC for sanity checking
+ * of the data.
+ *
+ */
+final class CRC {
+    static final int crc32Table[] = {
+        0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
+        0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
+        0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
+        0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+        0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
+        0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
+        0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
+        0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
+        0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+        0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
+        0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
+        0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
+        0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
+        0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+        0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
+        0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
+        0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
+        0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
+        0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+        0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
+        0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
+        0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
+        0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
+        0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+        0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
+        0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
+        0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
+        0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
+        0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+        0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
+        0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
+        0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
+        0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
+        0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+        0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
+        0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
+        0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
+        0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
+        0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+        0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
+        0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
+        0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
+        0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
+        0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+        0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
+        0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
+        0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
+        0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
+        0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+        0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
+        0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
+        0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
+        0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
+        0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+        0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
+        0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
+        0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
+        0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
+        0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+        0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
+        0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
+        0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
+        0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
+        0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+    };
+
+    CRC() {
+        initialiseCRC();
+    }
+
+    void initialiseCRC() {
+        globalCrc = 0xffffffff;
+    }
+
+    int getFinalCRC() {
+        return ~globalCrc;
+    }
+
+    int getGlobalCRC() {
+        return globalCrc;
+    }
+
+    void setGlobalCRC(int newCrc) {
+        globalCrc = newCrc;
+    }
+
+    void updateCRC(int inCh) {
+        int temp = (globalCrc >> 24) ^ inCh;
+        if (temp < 0) {
+            temp = 256 + temp;
+        }
+        globalCrc = (globalCrc << 8) ^ CRC.crc32Table[temp];
+    }
+
+    void updateCRC(int inCh, int repeat) {
+        int globalCrcShadow = this.globalCrc;
+        while (repeat-- > 0) {
+            int temp = (globalCrcShadow >> 24) ^ inCh;
+            globalCrcShadow = (globalCrcShadow << 8) ^ crc32Table[(temp >= 0)
+                                                      ? temp
+                                                      : (temp + 256)];
+        }
+        this.globalCrc = globalCrcShadow;
+    }
+
+    int globalCrc;
+}
+
diff --git a/src/org/json/simple/ItemList.java b/src/org/json/simple/ItemList.java
new file mode 100644 (file)
index 0000000..830961b
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * $Id: ItemList.java,v 1.1 2006/04/15 14:10:48 platform Exp $
+ * Created on 2006-3-24
+ */
+package org.json.simple;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * |a:b:c| => |a|,|b|,|c|
+ * |:| => ||,||
+ * |a:| => |a|,||
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public class ItemList {
+       private String sp=",";
+       List items=new ArrayList();
+       
+       
+       public ItemList(){}
+       
+       
+       public ItemList(String s){
+               this.split(s,sp,items);
+       }
+       
+       public ItemList(String s,String sp){
+               this.sp=s;
+               this.split(s,sp,items);
+       }
+       
+       public ItemList(String s,String sp,boolean isMultiToken){
+               split(s,sp,items,isMultiToken);
+       }
+       
+       public List getItems(){
+               return this.items;
+       }
+       
+       public String[] getArray(){
+               return (String[])this.items.toArray();
+       }
+       
+       public void split(String s,String sp,List append,boolean isMultiToken){
+               if(s==null || sp==null)
+                       return;
+               if(isMultiToken){
+                       StringTokenizer tokens=new StringTokenizer(s,sp);
+                       while(tokens.hasMoreTokens()){
+                               append.add(tokens.nextToken().trim());
+                       }
+               }
+               else{
+                       this.split(s,sp,append);
+               }
+       }
+       
+       public void split(String s,String sp,List append){
+               if(s==null || sp==null)
+                       return;
+               int pos=0;
+               int prevPos=0;
+               do{
+                       prevPos=pos;
+                       pos=s.indexOf(sp,pos);
+                       if(pos==-1)
+                               break;
+                       append.add(s.substring(prevPos,pos).trim());
+                       pos+=sp.length();
+               }while(pos!=-1);
+               append.add(s.substring(prevPos).trim());
+       }
+       
+       public void setSP(String sp){
+               this.sp=sp;
+       }
+       
+       public void add(int i,String item){
+               if(item==null)
+                       return;
+               items.add(i,item.trim());
+       }
+
+       public void add(String item){
+               if(item==null)
+                       return;
+               items.add(item.trim());
+       }
+       
+       public void addAll(ItemList list){
+               items.addAll(list.items);
+       }
+       
+       public void addAll(String s){
+               this.split(s,sp,items);
+       }
+       
+       public void addAll(String s,String sp){
+               this.split(s,sp,items);
+       }
+       
+       public void addAll(String s,String sp,boolean isMultiToken){
+               this.split(s,sp,items,isMultiToken);
+       }
+       
+       /**
+        * @param i 0-based
+        * @return
+        */
+       public String get(int i){
+               return (String)items.get(i);
+       }
+       
+       public int size(){
+               return items.size();
+       }
+
+       public String toString(){
+               return toString(sp);
+       }
+       
+       public String toString(String sp){
+               StringBuffer sb=new StringBuffer();
+               
+               for(int i=0;i<items.size();i++){
+                       if(i==0)
+                               sb.append(items.get(i));
+                       else{
+                               sb.append(sp);
+                               sb.append(items.get(i));
+                       }
+               }
+               return sb.toString();
+
+       }
+       
+       public void clear(){
+               items.clear();
+       }
+       
+       public void reset(){
+               sp=",";
+               items.clear();
+       }
+}
diff --git a/src/org/json/simple/JSONArray.java b/src/org/json/simple/JSONArray.java
new file mode 100644 (file)
index 0000000..60f54c4
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * $Id: JSONArray.java,v 1.1 2006/04/15 14:10:48 platform Exp $
+ * Created on 2006-4-10
+ */
+package org.json.simple;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * A JSON array. JSONObject supports java.util.List interface.
+ * 
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public class JSONArray extends ArrayList implements JSONAware, JSONStreamAware {
+       private static final long serialVersionUID = 3957988303675231981L;
+       
+       /**
+        * Constructs an empty JSONArray.
+        */
+       public JSONArray(){
+               super();
+       }
+       
+       /**
+        * Constructs a JSONArray containing the elements of the specified
+        * collection, in the order they are returned by the collection's iterator.
+        * 
+        * @param c the collection whose elements are to be placed into this JSONArray
+        */
+       public JSONArray(Collection c){
+               super(c);
+       }
+       
+    /**
+     * Encode a list into JSON text and write it to out. 
+     * If this list is also a JSONStreamAware or a JSONAware, JSONStreamAware and JSONAware specific behaviours will be ignored at this top level.
+     * 
+     * @see org.json.simple.JSONValue#writeJSONString(Object, Writer)
+     * 
+     * @param collection
+     * @param out
+     */
+       public static void writeJSONString(Collection collection, Writer out) throws IOException{
+               if(collection == null){
+                       out.write("null");
+                       return;
+               }
+               
+               boolean first = true;
+               Iterator iter=collection.iterator();
+               
+        out.write('[');
+               while(iter.hasNext()){
+            if(first)
+                first = false;
+            else
+                out.write(',');
+            
+                       Object value=iter.next();
+                       if(value == null){
+                               out.write("null");
+                               continue;
+                       }
+                       
+                       JSONValue.writeJSONString(value, out);
+               }
+               out.write(']');
+       }
+       
+       public void writeJSONString(Writer out) throws IOException{
+               writeJSONString(this, out);
+       }
+       
+       /**
+        * Convert a list to JSON text. The result is a JSON array. 
+        * If this list is also a JSONAware, JSONAware specific behaviours will be omitted at this top level.
+        * 
+        * @see org.json.simple.JSONValue#toJSONString(Object)
+        * 
+        * @param collection
+        * @return JSON text, or "null" if list is null.
+        */
+       public static String toJSONString(Collection collection){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(collection, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public static void writeJSONString(byte[] array, Writer out) throws IOException{
+               if(array == null){
+                       out.write("null");
+               } else if(array.length == 0) {
+                       out.write("[]");
+               } else {
+                       out.write("[");
+                       out.write(String.valueOf(array[0]));
+                       
+                       for(int i = 1; i < array.length; i++){
+                               out.write(",");
+                               out.write(String.valueOf(array[i]));
+                       }
+                       
+                       out.write("]");
+               }
+       }
+       
+       public static String toJSONString(byte[] array){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(array, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       public static void writeJSONString(short[] array, Writer out) throws IOException{
+               if(array == null){
+                       out.write("null");
+               } else if(array.length == 0) {
+                       out.write("[]");
+               } else {
+                       out.write("[");
+                       out.write(String.valueOf(array[0]));
+                       
+                       for(int i = 1; i < array.length; i++){
+                               out.write(",");
+                               out.write(String.valueOf(array[i]));
+                       }
+                       
+                       out.write("]");
+               }
+       }
+       
+       public static String toJSONString(short[] array){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(array, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       public static void writeJSONString(int[] array, Writer out) throws IOException{
+               if(array == null){
+                       out.write("null");
+               } else if(array.length == 0) {
+                       out.write("[]");
+               } else {
+                       out.write("[");
+                       out.write(String.valueOf(array[0]));
+                       
+                       for(int i = 1; i < array.length; i++){
+                               out.write(",");
+                               out.write(String.valueOf(array[i]));
+                       }
+                       
+                       out.write("]");
+               }
+       }
+       
+       public static String toJSONString(int[] array){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(array, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       public static void writeJSONString(long[] array, Writer out) throws IOException{
+               if(array == null){
+                       out.write("null");
+               } else if(array.length == 0) {
+                       out.write("[]");
+               } else {
+                       out.write("[");
+                       out.write(String.valueOf(array[0]));
+                       
+                       for(int i = 1; i < array.length; i++){
+                               out.write(",");
+                               out.write(String.valueOf(array[i]));
+                       }
+                       
+                       out.write("]");
+               }
+       }
+       
+       public static String toJSONString(long[] array){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(array, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       public static void writeJSONString(float[] array, Writer out) throws IOException{
+               if(array == null){
+                       out.write("null");
+               } else if(array.length == 0) {
+                       out.write("[]");
+               } else {
+                       out.write("[");
+                       out.write(String.valueOf(array[0]));
+                       
+                       for(int i = 1; i < array.length; i++){
+                               out.write(",");
+                               out.write(String.valueOf(array[i]));
+                       }
+                       
+                       out.write("]");
+               }
+       }
+       
+       public static String toJSONString(float[] array){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(array, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       public static void writeJSONString(double[] array, Writer out) throws IOException{
+               if(array == null){
+                       out.write("null");
+               } else if(array.length == 0) {
+                       out.write("[]");
+               } else {
+                       out.write("[");
+                       out.write(String.valueOf(array[0]));
+                       
+                       for(int i = 1; i < array.length; i++){
+                               out.write(",");
+                               out.write(String.valueOf(array[i]));
+                       }
+                       
+                       out.write("]");
+               }
+       }
+       
+       public static String toJSONString(double[] array){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(array, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       public static void writeJSONString(boolean[] array, Writer out) throws IOException{
+               if(array == null){
+                       out.write("null");
+               } else if(array.length == 0) {
+                       out.write("[]");
+               } else {
+                       out.write("[");
+                       out.write(String.valueOf(array[0]));
+                       
+                       for(int i = 1; i < array.length; i++){
+                               out.write(",");
+                               out.write(String.valueOf(array[i]));
+                       }
+                       
+                       out.write("]");
+               }
+       }
+       
+       public static String toJSONString(boolean[] array){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(array, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       public static void writeJSONString(char[] array, Writer out) throws IOException{
+               if(array == null){
+                       out.write("null");
+               } else if(array.length == 0) {
+                       out.write("[]");
+               } else {
+                       out.write("[\"");
+                       out.write(String.valueOf(array[0]));
+                       
+                       for(int i = 1; i < array.length; i++){
+                               out.write("\",\"");
+                               out.write(String.valueOf(array[i]));
+                       }
+                       
+                       out.write("\"]");
+               }
+       }
+       
+       public static String toJSONString(char[] array){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(array, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       public static void writeJSONString(Object[] array, Writer out) throws IOException{
+               if(array == null){
+                       out.write("null");
+               } else if(array.length == 0) {
+                       out.write("[]");
+               } else {
+                       out.write("[");
+                       JSONValue.writeJSONString(array[0], out);
+                       
+                       for(int i = 1; i < array.length; i++){
+                               out.write(",");
+                               JSONValue.writeJSONString(array[i], out);
+                       }
+                       
+                       out.write("]");
+               }
+       }
+       
+       public static String toJSONString(Object[] array){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(array, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       public String toJSONString(){
+               return toJSONString(this);
+       }
+
+       /**
+        * Returns a string representation of this array. This is equivalent to
+        * calling {@link JSONArray#toJSONString()}.
+        */
+       public String toString() {
+               return toJSONString();
+       }
+}
diff --git a/src/org/json/simple/JSONAware.java b/src/org/json/simple/JSONAware.java
new file mode 100644 (file)
index 0000000..5e7452f
--- /dev/null
@@ -0,0 +1,12 @@
+package org.json.simple;
+
+/**
+ * Beans that support customized output of JSON text shall implement this interface.  
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public interface JSONAware {
+       /**
+        * @return JSON text
+        */
+       String toJSONString();
+}
diff --git a/src/org/json/simple/JSONObject.java b/src/org/json/simple/JSONObject.java
new file mode 100644 (file)
index 0000000..fafa36b
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * $Id: JSONObject.java,v 1.1 2006/04/15 14:10:48 platform Exp $
+ * Created on 2006-4-10
+ */
+package org.json.simple;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A JSON object. Key value pairs are unordered. JSONObject supports java.util.Map interface.
+ * 
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public class JSONObject extends HashMap implements Map, JSONAware, JSONStreamAware{
+       
+       private static final long serialVersionUID = -503443796854799292L;
+       
+       
+       public JSONObject() {
+               super();
+       }
+
+       /**
+        * Allows creation of a JSONObject from a Map. After that, both the
+        * generated JSONObject and the Map can be modified independently.
+        * 
+        * @param map
+        */
+       public JSONObject(Map map) {
+               super(map);
+       }
+
+
+    /**
+     * Encode a map into JSON text and write it to out.
+     * If this map is also a JSONAware or JSONStreamAware, JSONAware or JSONStreamAware specific behaviours will be ignored at this top level.
+     * 
+     * @see org.json.simple.JSONValue#writeJSONString(Object, Writer)
+     * 
+     * @param map
+     * @param out
+     */
+       public static void writeJSONString(Map map, Writer out) throws IOException {
+               if(map == null){
+                       out.write("null");
+                       return;
+               }
+               
+               boolean first = true;
+               Iterator iter=map.entrySet().iterator();
+               
+        out.write('{');
+               while(iter.hasNext()){
+            if(first)
+                first = false;
+            else
+                out.write(',');
+                       Map.Entry entry=(Map.Entry)iter.next();
+            out.write('\"');
+            out.write(escape(String.valueOf(entry.getKey())));
+            out.write('\"');
+            out.write(':');
+                       JSONValue.writeJSONString(entry.getValue(), out);
+               }
+               out.write('}');
+       }
+
+       public void writeJSONString(Writer out) throws IOException{
+               writeJSONString(this, out);
+       }
+       
+       /**
+        * Convert a map to JSON text. The result is a JSON object. 
+        * If this map is also a JSONAware, JSONAware specific behaviours will be omitted at this top level.
+        * 
+        * @see org.json.simple.JSONValue#toJSONString(Object)
+        * 
+        * @param map
+        * @return JSON text, or "null" if map is null.
+        */
+       public static String toJSONString(Map map){
+               final StringWriter writer = new StringWriter();
+               
+               try {
+                       writeJSONString(map, writer);
+                       return writer.toString();
+               } catch (IOException e) {
+                       // This should never happen with a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       public String toJSONString(){
+               return toJSONString(this);
+       }
+       
+       public String toString(){
+               return toJSONString();
+       }
+
+       public static String toString(String key,Object value){
+        StringBuffer sb = new StringBuffer();
+        sb.append('\"');
+        if(key == null)
+            sb.append("null");
+        else
+            JSONValue.escape(key, sb);
+               sb.append('\"').append(':');
+               
+               sb.append(JSONValue.toJSONString(value));
+               
+               return sb.toString();
+       }
+       
+       /**
+        * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F).
+        * It's the same as JSONValue.escape() only for compatibility here.
+        * 
+        * @see org.json.simple.JSONValue#escape(String)
+        * 
+        * @param s
+        * @return
+        */
+       public static String escape(String s){
+               return JSONValue.escape(s);
+       }
+}
diff --git a/src/org/json/simple/JSONStreamAware.java b/src/org/json/simple/JSONStreamAware.java
new file mode 100644 (file)
index 0000000..ab63b3e
--- /dev/null
@@ -0,0 +1,15 @@
+package org.json.simple;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Beans that support customized output of JSON text to a writer shall implement this interface.  
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public interface JSONStreamAware {
+       /**
+        * write JSON string to out.
+        */
+       void writeJSONString(Writer out) throws IOException;
+}
diff --git a/src/org/json/simple/JSONValue.java b/src/org/json/simple/JSONValue.java
new file mode 100644 (file)
index 0000000..5da3cd0
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+ * $Id: JSONValue.java,v 1.1 2006/04/15 14:37:04 platform Exp $
+ * Created on 2006-4-15
+ */
+package org.json.simple;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Collection;
+// import java.util.List;
+import java.util.Map;
+
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+
+/**
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public class JSONValue {
+       /**
+        * Parse JSON text into java object from the input source. 
+        * Please use parseWithException() if you don't want to ignore the exception.
+        * 
+        * @see org.json.simple.parser.JSONParser#parse(Reader)
+        * @see #parseWithException(Reader)
+        * 
+        * @param in
+        * @return Instance of the following:
+        *      org.json.simple.JSONObject,
+        *      org.json.simple.JSONArray,
+        *      java.lang.String,
+        *      java.lang.Number,
+        *      java.lang.Boolean,
+        *      null
+        * 
+        * @deprecated this method may throw an {@code Error} instead of returning
+        * {@code null}; please use {@link JSONValue#parseWithException(Reader)}
+        * instead
+        */
+       public static Object parse(Reader in){
+               try{
+                       JSONParser parser=new JSONParser();
+                       return parser.parse(in);
+               }
+               catch(Exception e){
+                       return null;
+               }
+       }
+       
+       /**
+        * Parse JSON text into java object from the given string. 
+        * Please use parseWithException() if you don't want to ignore the exception.
+        * 
+        * @see org.json.simple.parser.JSONParser#parse(Reader)
+        * @see #parseWithException(Reader)
+        * 
+        * @param s
+        * @return Instance of the following:
+        *      org.json.simple.JSONObject,
+        *      org.json.simple.JSONArray,
+        *      java.lang.String,
+        *      java.lang.Number,
+        *      java.lang.Boolean,
+        *      null
+        * 
+        * @deprecated this method may throw an {@code Error} instead of returning
+        * {@code null}; please use {@link JSONValue#parseWithException(String)}
+        * instead
+        */
+       public static Object parse(String s){
+               StringReader in=new StringReader(s);
+               return parse(in);
+       }
+       
+       /**
+        * Parse JSON text into java object from the input source.
+        * 
+        * @see org.json.simple.parser.JSONParser
+        * 
+        * @param in
+        * @return Instance of the following:
+        *      org.json.simple.JSONObject,
+        *      org.json.simple.JSONArray,
+        *      java.lang.String,
+        *      java.lang.Number,
+        *      java.lang.Boolean,
+        *      null
+        * 
+        * @throws IOException
+        * @throws ParseException
+        */
+       public static Object parseWithException(Reader in) throws IOException, ParseException{
+               JSONParser parser=new JSONParser();
+               return parser.parse(in);
+       }
+       
+       public static Object parseWithException(String s) throws ParseException{
+               JSONParser parser=new JSONParser();
+               return parser.parse(s);
+       }
+       
+    /**
+     * Encode an object into JSON text and write it to out.
+     * <p>
+     * If this object is a Map or a List, and it's also a JSONStreamAware or a JSONAware, JSONStreamAware or JSONAware will be considered firstly.
+     * <p>
+     * DO NOT call this method from writeJSONString(Writer) of a class that implements both JSONStreamAware and (Map or List) with 
+     * "this" as the first parameter, use JSONObject.writeJSONString(Map, Writer) or JSONArray.writeJSONString(List, Writer) instead. 
+     * 
+     * @see org.json.simple.JSONObject#writeJSONString(Map, Writer)
+     * @see org.json.simple.JSONArray#writeJSONString(List, Writer)
+     * 
+     * @param value
+     * @param writer
+     */
+       public static void writeJSONString(Object value, Writer out) throws IOException {
+               if(value == null){
+                       out.write("null");
+                       return;
+               }
+               
+               if(value instanceof String){            
+            out.write('\"');
+                       out.write(escape((String)value));
+            out.write('\"');
+                       return;
+               }
+               
+               if(value instanceof Double){
+                       if(((Double)value).isInfinite() || ((Double)value).isNaN())
+                               out.write("null");
+                       else
+                               out.write(value.toString());
+                       return;
+               }
+               
+               if(value instanceof Float){
+                       if(((Float)value).isInfinite() || ((Float)value).isNaN())
+                               out.write("null");
+                       else
+                               out.write(value.toString());
+                       return;
+               }               
+               
+               if(value instanceof Number){
+                       out.write(value.toString());
+                       return;
+               }
+               
+               if(value instanceof Boolean){
+                       out.write(value.toString());
+                       return;
+               }
+               
+               if((value instanceof JSONStreamAware)){
+                       ((JSONStreamAware)value).writeJSONString(out);
+                       return;
+               }
+               
+               if((value instanceof JSONAware)){
+                       out.write(((JSONAware)value).toJSONString());
+                       return;
+               }
+               
+               if(value instanceof Map){
+                       JSONObject.writeJSONString((Map)value, out);
+                       return;
+               }
+               
+               if(value instanceof Collection){
+                       JSONArray.writeJSONString((Collection)value, out);
+            return;
+               }
+               
+               if(value instanceof byte[]){
+                       JSONArray.writeJSONString((byte[])value, out);
+                       return;
+               }
+               
+               if(value instanceof short[]){
+                       JSONArray.writeJSONString((short[])value, out);
+                       return;
+               }
+               
+               if(value instanceof int[]){
+                       JSONArray.writeJSONString((int[])value, out);
+                       return;
+               }
+               
+               if(value instanceof long[]){
+                       JSONArray.writeJSONString((long[])value, out);
+                       return;
+               }
+               
+               if(value instanceof float[]){
+                       JSONArray.writeJSONString((float[])value, out);
+                       return;
+               }
+               
+               if(value instanceof double[]){
+                       JSONArray.writeJSONString((double[])value, out);
+                       return;
+               }
+               
+               if(value instanceof boolean[]){
+                       JSONArray.writeJSONString((boolean[])value, out);
+                       return;
+               }
+               
+               if(value instanceof char[]){
+                       JSONArray.writeJSONString((char[])value, out);
+                       return;
+               }
+               
+               if(value instanceof Object[]){
+                       JSONArray.writeJSONString((Object[])value, out);
+                       return;
+               }
+               
+               out.write(value.toString());
+       }
+
+       /**
+        * Convert an object to JSON text.
+        * <p>
+        * If this object is a Map or a List, and it's also a JSONAware, JSONAware will be considered firstly.
+        * <p>
+        * DO NOT call this method from toJSONString() of a class that implements both JSONAware and Map or List with 
+        * "this" as the parameter, use JSONObject.toJSONString(Map) or JSONArray.toJSONString(List) instead. 
+        * 
+        * @see org.json.simple.JSONObject#toJSONString(Map)
+        * @see org.json.simple.JSONArray#toJSONString(List)
+        * 
+        * @param value
+        * @return JSON text, or "null" if value is null or it's an NaN or an INF number.
+        */
+       public static String toJSONString(Object value){
+               final StringWriter writer = new StringWriter();
+               
+               try{
+                       writeJSONString(value, writer);
+                       return writer.toString();
+               } catch(IOException e){
+                       // This should never happen for a StringWriter
+                       throw new RuntimeException(e);
+               }
+       }
+
+       /**
+        * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F).
+        * @param s
+        * @return
+        */
+       public static String escape(String s){
+               if(s==null)
+                       return null;
+        StringBuffer sb = new StringBuffer();
+        escape(s, sb);
+        return sb.toString();
+    }
+
+    /**
+     * @param s - Must not be null.
+     * @param sb
+     */
+    static void escape(String s, StringBuffer sb) {
+       final int len = s.length();
+               for(int i=0;i<len;i++){
+                       char ch=s.charAt(i);
+                       switch(ch){
+                       case '"':
+                               sb.append("\\\"");
+                               break;
+                       case '\\':
+                               sb.append("\\\\");
+                               break;
+                       case '\b':
+                               sb.append("\\b");
+                               break;
+                       case '\f':
+                               sb.append("\\f");
+                               break;
+                       case '\n':
+                               sb.append("\\n");
+                               break;
+                       case '\r':
+                               sb.append("\\r");
+                               break;
+                       case '\t':
+                               sb.append("\\t");
+                               break;
+                       case '/':
+                               sb.append("\\/");
+                               break;
+                       default:
+                //Reference: http://www.unicode.org/versions/Unicode5.1.0/
+                               if((ch>='\u0000' && ch<='\u001F') || (ch>='\u007F' && ch<='\u009F') || (ch>='\u2000' && ch<='\u20FF')){
+                                       String ss=Integer.toHexString(ch);
+                                       sb.append("\\u");
+                                       for(int k=0;k<4-ss.length();k++){
+                                               sb.append('0');
+                                       }
+                                       sb.append(ss.toUpperCase());
+                               }
+                               else{
+                                       sb.append(ch);
+                               }
+                       }
+               }//for
+       }
+
+}
diff --git a/src/org/json/simple/README.txt b/src/org/json/simple/README.txt
new file mode 100644 (file)
index 0000000..43b3a6c
--- /dev/null
@@ -0,0 +1,4 @@
+https://github.com/SwingJS/json-simple.git
+forked 6/23/2018 from https://github.com/fangyidong/json-simple/tree/master/src/main/java/org/json/simple
+Bob Hanson
+hansonr@stolaf.edu
\ No newline at end of file
diff --git a/src/org/json/simple/parser/ContainerFactory.java b/src/org/json/simple/parser/ContainerFactory.java
new file mode 100644 (file)
index 0000000..0bb7baf
--- /dev/null
@@ -0,0 +1,23 @@
+package org.json.simple.parser;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Container factory for creating containers for JSON object and JSON array.
+ * 
+ * @see org.json.simple.parser.JSONParser#parse(java.io.Reader, ContainerFactory)
+ * 
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public interface ContainerFactory {
+       /**
+        * @return A Map instance to store JSON object, or null if you want to use org.json.simple.JSONObject.
+        */
+       Map createObjectContainer();
+       
+       /**
+        * @return A List instance to store JSON array, or null if you want to use org.json.simple.JSONArray. 
+        */
+       List creatArrayContainer();
+}
diff --git a/src/org/json/simple/parser/ContentHandler.java b/src/org/json/simple/parser/ContentHandler.java
new file mode 100644 (file)
index 0000000..056a85c
--- /dev/null
@@ -0,0 +1,110 @@
+package org.json.simple.parser;
+
+import java.io.IOException;
+
+/**
+ * A simplified and stoppable SAX-like content handler for stream processing of JSON text. 
+ * 
+ * @see org.xml.sax.ContentHandler
+ * @see org.json.simple.parser.JSONParser#parse(java.io.Reader, ContentHandler, boolean)
+ * 
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public interface ContentHandler {
+       /**
+        * Receive notification of the beginning of JSON processing.
+        * The parser will invoke this method only once.
+     * 
+        * @throws ParseException 
+        *                      - JSONParser will stop and throw the same exception to the caller when receiving this exception.
+        */
+       void startJSON() throws ParseException, IOException;
+       
+       /**
+        * Receive notification of the end of JSON processing.
+        * 
+        * @throws ParseException
+        */
+       void endJSON() throws ParseException, IOException;
+       
+       /**
+        * Receive notification of the beginning of a JSON object.
+        * 
+        * @return false if the handler wants to stop parsing after return.
+        * @throws ParseException
+     *          - JSONParser will stop and throw the same exception to the caller when receiving this exception.
+     * @see #endJSON
+        */
+       boolean startObject() throws ParseException, IOException;
+       
+       /**
+        * Receive notification of the end of a JSON object.
+        * 
+        * @return false if the handler wants to stop parsing after return.
+        * @throws ParseException
+     * 
+     * @see #startObject
+        */
+       boolean endObject() throws ParseException, IOException;
+       
+       /**
+        * Receive notification of the beginning of a JSON object entry.
+        * 
+        * @param key - Key of a JSON object entry. 
+        * 
+        * @return false if the handler wants to stop parsing after return.
+        * @throws ParseException
+     * 
+     * @see #endObjectEntry
+        */
+       boolean startObjectEntry(String key) throws ParseException, IOException;
+       
+       /**
+        * Receive notification of the end of the value of previous object entry.
+        * 
+        * @return false if the handler wants to stop parsing after return.
+        * @throws ParseException
+     * 
+     * @see #startObjectEntry
+        */
+       boolean endObjectEntry() throws ParseException, IOException;
+       
+       /**
+        * Receive notification of the beginning of a JSON array.
+        * 
+        * @return false if the handler wants to stop parsing after return.
+        * @throws ParseException
+     * 
+     * @see #endArray
+        */
+       boolean startArray() throws ParseException, IOException;
+       
+       /**
+        * Receive notification of the end of a JSON array.
+        * 
+        * @return false if the handler wants to stop parsing after return.
+        * @throws ParseException
+     * 
+     * @see #startArray
+        */
+       boolean endArray() throws ParseException, IOException;
+       
+       /**
+        * Receive notification of the JSON primitive values:
+        *      java.lang.String,
+        *      java.lang.Number,
+        *      java.lang.Boolean
+        *      null
+        * 
+        * @param value - Instance of the following:
+        *                      java.lang.String,
+        *                      java.lang.Number,
+        *                      java.lang.Boolean
+        *                      null
+        * 
+        * @return false if the handler wants to stop parsing after return.
+        * @throws ParseException
+        */
+       boolean primitive(Object value) throws ParseException, IOException;
+               
+}
diff --git a/src/org/json/simple/parser/JSONParser.java b/src/org/json/simple/parser/JSONParser.java
new file mode 100644 (file)
index 0000000..59f8e31
--- /dev/null
@@ -0,0 +1,533 @@
+/*
+ * $Id: JSONParser.java,v 1.1 2006/04/15 14:10:48 platform Exp $
+ * Created on 2006-4-15
+ */
+package org.json.simple.parser;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+
+
+/**
+ * Parser for JSON text. Please note that JSONParser is NOT thread-safe.
+ * 
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public class JSONParser {
+       public static final int S_INIT=0;
+       public static final int S_IN_FINISHED_VALUE=1;//string,number,boolean,null,object,array
+       public static final int S_IN_OBJECT=2;
+       public static final int S_IN_ARRAY=3;
+       public static final int S_PASSED_PAIR_KEY=4;
+       public static final int S_IN_PAIR_VALUE=5;
+       public static final int S_END=6;
+       public static final int S_IN_ERROR=-1;
+       
+       private LinkedList handlerStatusStack;
+       private Yylex lexer = new Yylex((Reader)null);
+       private Yytoken token = null;
+       private int status = S_INIT;
+       
+       private int peekStatus(LinkedList statusStack){
+               if(statusStack.size()==0)
+                       return -1;
+               Integer status=(Integer)statusStack.getFirst();
+               return status.intValue();
+       }
+       
+    /**
+     *  Reset the parser to the initial state without resetting the underlying reader.
+     *
+     */
+    public void reset(){
+        token = null;
+        status = S_INIT;
+        handlerStatusStack = null;
+    }
+    
+    /**
+     * Reset the parser to the initial state with a new character reader.
+     * 
+     * @param in - The new character reader.
+     * @throws IOException
+     * @throws ParseException
+     */
+       public void reset(Reader in){
+               lexer.yyreset(in);
+               reset();
+       }
+       
+       /**
+        * @return The position of the beginning of the current token.
+        */
+       public int getPosition(){
+               return lexer.getPosition();
+       }
+       
+       public Object parse(String s) throws ParseException{
+               return parse(s, (ContainerFactory)null);
+       }
+       
+       public Object parse(String s, ContainerFactory containerFactory) throws ParseException{
+               StringReader in=new StringReader(s);
+               try{
+                       return parse(in, containerFactory);
+               }
+               catch(IOException ie){
+                       /*
+                        * Actually it will never happen.
+                        */
+                       throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie);
+               }
+       }
+       
+       public Object parse(Reader in) throws IOException, ParseException{
+               return parse(in, (ContainerFactory)null);
+       }
+       
+       /**
+        * Parse JSON text into java object from the input source.
+        *      
+        * @param in
+     * @param containerFactory - Use this factory to createyour own JSON object and JSON array containers.
+        * @return Instance of the following:
+        *  org.json.simple.JSONObject,
+        *      org.json.simple.JSONArray,
+        *      java.lang.String,
+        *      java.lang.Number,
+        *      java.lang.Boolean,
+        *      null
+        * 
+        * @throws IOException
+        * @throws ParseException
+        */
+       public Object parse(Reader in, ContainerFactory containerFactory) throws IOException, ParseException{
+               reset(in);
+               LinkedList statusStack = new LinkedList();
+               LinkedList valueStack = new LinkedList();
+               
+               try{
+                       do{
+                               nextToken();
+                               switch(status){
+                               case S_INIT:
+                                       switch(token.type){
+                                       case Yytoken.TYPE_VALUE:
+                                               status=S_IN_FINISHED_VALUE;
+                                               statusStack.addFirst(new Integer(status));
+                                               valueStack.addFirst(token.value);
+                                               break;
+                                       case Yytoken.TYPE_LEFT_BRACE:
+                                               status=S_IN_OBJECT;
+                                               statusStack.addFirst(new Integer(status));
+                                               valueStack.addFirst(createObjectContainer(containerFactory));
+                                               break;
+                                       case Yytoken.TYPE_LEFT_SQUARE:
+                                               status=S_IN_ARRAY;
+                                               statusStack.addFirst(new Integer(status));
+                                               valueStack.addFirst(createArrayContainer(containerFactory));
+                                               break;
+                                       default:
+                                               status=S_IN_ERROR;
+                                       }//inner switch
+                                       break;
+                                       
+                               case S_IN_FINISHED_VALUE:
+                                       if(token.type==Yytoken.TYPE_EOF)
+                                               return valueStack.removeFirst();
+                                       else
+                                               throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
+                                       
+                               case S_IN_OBJECT:
+                                       switch(token.type){
+                                       case Yytoken.TYPE_COMMA:
+                                               break;
+                                       case Yytoken.TYPE_VALUE:
+                                               if(token.value instanceof String){
+                                                       String key=(String)token.value;
+                                                       valueStack.addFirst(key);
+                                                       status=S_PASSED_PAIR_KEY;
+                                                       statusStack.addFirst(new Integer(status));
+                                               }
+                                               else{
+                                                       status=S_IN_ERROR;
+                                               }
+                                               break;
+                                       case Yytoken.TYPE_RIGHT_BRACE:
+                                               if(valueStack.size()>1){
+                                                       statusStack.removeFirst();
+                                                       valueStack.removeFirst();
+                                                       status=peekStatus(statusStack);
+                                               }
+                                               else{
+                                                       status=S_IN_FINISHED_VALUE;
+                                               }
+                                               break;
+                                       default:
+                                               status=S_IN_ERROR;
+                                               break;
+                                       }//inner switch
+                                       break;
+                                       
+                               case S_PASSED_PAIR_KEY:
+                                       switch(token.type){
+                                       case Yytoken.TYPE_COLON:
+                                               break;
+                                       case Yytoken.TYPE_VALUE:
+                                               statusStack.removeFirst();
+                                               String key=(String)valueStack.removeFirst();
+                                               Map parent=(Map)valueStack.getFirst();
+                                               parent.put(key,token.value);
+                                               status=peekStatus(statusStack);
+                                               break;
+                                       case Yytoken.TYPE_LEFT_SQUARE:
+                                               statusStack.removeFirst();
+                                               key=(String)valueStack.removeFirst();
+                                               parent=(Map)valueStack.getFirst();
+                                               List newArray=createArrayContainer(containerFactory);
+                                               parent.put(key,newArray);
+                                               status=S_IN_ARRAY;
+                                               statusStack.addFirst(new Integer(status));
+                                               valueStack.addFirst(newArray);
+                                               break;
+                                       case Yytoken.TYPE_LEFT_BRACE:
+                                               statusStack.removeFirst();
+                                               key=(String)valueStack.removeFirst();
+                                               parent=(Map)valueStack.getFirst();
+                                               Map newObject=createObjectContainer(containerFactory);
+                                               parent.put(key,newObject);
+                                               status=S_IN_OBJECT;
+                                               statusStack.addFirst(new Integer(status));
+                                               valueStack.addFirst(newObject);
+                                               break;
+                                       default:
+                                               status=S_IN_ERROR;
+                                       }
+                                       break;
+                                       
+                               case S_IN_ARRAY:
+                                       switch(token.type){
+                                       case Yytoken.TYPE_COMMA:
+                                               break;
+                                       case Yytoken.TYPE_VALUE:
+                                               List val=(List)valueStack.getFirst();
+                                               val.add(token.value);
+                                               break;
+                                       case Yytoken.TYPE_RIGHT_SQUARE:
+                                               if(valueStack.size()>1){
+                                                       statusStack.removeFirst();
+                                                       valueStack.removeFirst();
+                                                       status=peekStatus(statusStack);
+                                               }
+                                               else{
+                                                       status=S_IN_FINISHED_VALUE;
+                                               }
+                                               break;
+                                       case Yytoken.TYPE_LEFT_BRACE:
+                                               val=(List)valueStack.getFirst();
+                                               Map newObject=createObjectContainer(containerFactory);
+                                               val.add(newObject);
+                                               status=S_IN_OBJECT;
+                                               statusStack.addFirst(new Integer(status));
+                                               valueStack.addFirst(newObject);
+                                               break;
+                                       case Yytoken.TYPE_LEFT_SQUARE:
+                                               val=(List)valueStack.getFirst();
+                                               List newArray=createArrayContainer(containerFactory);
+                                               val.add(newArray);
+                                               status=S_IN_ARRAY;
+                                               statusStack.addFirst(new Integer(status));
+                                               valueStack.addFirst(newArray);
+                                               break;
+                                       default:
+                                               status=S_IN_ERROR;
+                                       }//inner switch
+                                       break;
+                               case S_IN_ERROR:
+                                       throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
+                               }//switch
+                               if(status==S_IN_ERROR){
+                                       throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
+                               }
+                       }while(token.type!=Yytoken.TYPE_EOF);
+               }
+               catch(IOException ie){
+                       throw ie;
+               }
+               
+               throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
+       }
+       
+       private void nextToken() throws ParseException, IOException{
+               token = lexer.yylex();
+               if(token == null)
+                       token = new Yytoken(Yytoken.TYPE_EOF, null);
+       }
+       
+       private Map createObjectContainer(ContainerFactory containerFactory){
+               if(containerFactory == null)
+                       return new JSONObject();
+               Map m = containerFactory.createObjectContainer();
+               
+               if(m == null)
+                       return new JSONObject();
+               return m;
+       }
+       
+       private List createArrayContainer(ContainerFactory containerFactory){
+               if(containerFactory == null)
+                       return new JSONArray();
+               List l = containerFactory.creatArrayContainer();
+               
+               if(l == null)
+                       return new JSONArray();
+               return l;
+       }
+       
+       public void parse(String s, ContentHandler contentHandler) throws ParseException{
+               parse(s, contentHandler, false);
+       }
+       
+       public void parse(String s, ContentHandler contentHandler, boolean isResume) throws ParseException{
+               StringReader in=new StringReader(s);
+               try{
+                       parse(in, contentHandler, isResume);
+               }
+               catch(IOException ie){
+                       /*
+                        * Actually it will never happen.
+                        */
+                       throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie);
+               }
+       }
+       
+       public void parse(Reader in, ContentHandler contentHandler) throws IOException, ParseException{
+               parse(in, contentHandler, false);
+       }
+       
+       /**
+        * Stream processing of JSON text.
+        * 
+        * @see ContentHandler
+        * 
+        * @param in
+        * @param contentHandler
+        * @param isResume - Indicates if it continues previous parsing operation.
+     *                   If set to true, resume parsing the old stream, and parameter 'in' will be ignored. 
+        *                   If this method is called for the first time in this instance, isResume will be ignored.
+        * 
+        * @throws IOException
+        * @throws ParseException
+        */
+       public void parse(Reader in, ContentHandler contentHandler, boolean isResume) throws IOException, ParseException{
+               if(!isResume){
+                       reset(in);
+                       handlerStatusStack = new LinkedList();
+               }
+               else{
+                       if(handlerStatusStack == null){
+                               isResume = false;
+                               reset(in);
+                               handlerStatusStack = new LinkedList();
+                       }
+               }
+               
+               LinkedList statusStack = handlerStatusStack;    
+               
+               try{
+                       do{
+                               switch(status){
+                               case S_INIT:
+                                       contentHandler.startJSON();
+                                       nextToken();
+                                       switch(token.type){
+                                       case Yytoken.TYPE_VALUE:
+                                               status=S_IN_FINISHED_VALUE;
+                                               statusStack.addFirst(new Integer(status));
+                                               if(!contentHandler.primitive(token.value))
+                                                       return;
+                                               break;
+                                       case Yytoken.TYPE_LEFT_BRACE:
+                                               status=S_IN_OBJECT;
+                                               statusStack.addFirst(new Integer(status));
+                                               if(!contentHandler.startObject())
+                                                       return;
+                                               break;
+                                       case Yytoken.TYPE_LEFT_SQUARE:
+                                               status=S_IN_ARRAY;
+                                               statusStack.addFirst(new Integer(status));
+                                               if(!contentHandler.startArray())
+                                                       return;
+                                               break;
+                                       default:
+                                               status=S_IN_ERROR;
+                                       }//inner switch
+                                       break;
+                                       
+                               case S_IN_FINISHED_VALUE:
+                                       nextToken();
+                                       if(token.type==Yytoken.TYPE_EOF){
+                                               contentHandler.endJSON();
+                                               status = S_END;
+                                               return;
+                                       }
+                                       else{
+                                               status = S_IN_ERROR;
+                                               throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
+                                       }
+                       
+                               case S_IN_OBJECT:
+                                       nextToken();
+                                       switch(token.type){
+                                       case Yytoken.TYPE_COMMA:
+                                               break;
+                                       case Yytoken.TYPE_VALUE:
+                                               if(token.value instanceof String){
+                                                       String key=(String)token.value;
+                                                       status=S_PASSED_PAIR_KEY;
+                                                       statusStack.addFirst(new Integer(status));
+                                                       if(!contentHandler.startObjectEntry(key))
+                                                               return;
+                                               }
+                                               else{
+                                                       status=S_IN_ERROR;
+                                               }
+                                               break;
+                                       case Yytoken.TYPE_RIGHT_BRACE:
+                                               if(statusStack.size()>1){
+                                                       statusStack.removeFirst();
+                                                       status=peekStatus(statusStack);
+                                               }
+                                               else{
+                                                       status=S_IN_FINISHED_VALUE;
+                                               }
+                                               if(!contentHandler.endObject())
+                                                       return;
+                                               break;
+                                       default:
+                                               status=S_IN_ERROR;
+                                               break;
+                                       }//inner switch
+                                       break;
+                                       
+                               case S_PASSED_PAIR_KEY:
+                                       nextToken();
+                                       switch(token.type){
+                                       case Yytoken.TYPE_COLON:
+                                               break;
+                                       case Yytoken.TYPE_VALUE:
+                                               statusStack.removeFirst();
+                                               status=peekStatus(statusStack);
+                                               if(!contentHandler.primitive(token.value))
+                                                       return;
+                                               if(!contentHandler.endObjectEntry())
+                                                       return;
+                                               break;
+                                       case Yytoken.TYPE_LEFT_SQUARE:
+                                               statusStack.removeFirst();
+                                               statusStack.addFirst(new Integer(S_IN_PAIR_VALUE));
+                                               status=S_IN_ARRAY;
+                                               statusStack.addFirst(new Integer(status));
+                                               if(!contentHandler.startArray())
+                                                       return;
+                                               break;
+                                       case Yytoken.TYPE_LEFT_BRACE:
+                                               statusStack.removeFirst();
+                                               statusStack.addFirst(new Integer(S_IN_PAIR_VALUE));
+                                               status=S_IN_OBJECT;
+                                               statusStack.addFirst(new Integer(status));
+                                               if(!contentHandler.startObject())
+                                                       return;
+                                               break;
+                                       default:
+                                               status=S_IN_ERROR;
+                                       }
+                                       break;
+                               
+                               case S_IN_PAIR_VALUE:
+                                       /*
+                                        * S_IN_PAIR_VALUE is just a marker to indicate the end of an object entry, it doesn't proccess any token,
+                                        * therefore delay consuming token until next round.
+                                        */
+                                       statusStack.removeFirst();
+                                       status = peekStatus(statusStack);
+                                       if(!contentHandler.endObjectEntry())
+                                               return;
+                                       break;
+                                       
+                               case S_IN_ARRAY:
+                                       nextToken();
+                                       switch(token.type){
+                                       case Yytoken.TYPE_COMMA:
+                                               break;
+                                       case Yytoken.TYPE_VALUE:
+                                               if(!contentHandler.primitive(token.value))
+                                                       return;
+                                               break;
+                                       case Yytoken.TYPE_RIGHT_SQUARE:
+                                               if(statusStack.size()>1){
+                                                       statusStack.removeFirst();
+                                                       status=peekStatus(statusStack);
+                                               }
+                                               else{
+                                                       status=S_IN_FINISHED_VALUE;
+                                               }
+                                               if(!contentHandler.endArray())
+                                                       return;
+                                               break;
+                                       case Yytoken.TYPE_LEFT_BRACE:
+                                               status=S_IN_OBJECT;
+                                               statusStack.addFirst(new Integer(status));
+                                               if(!contentHandler.startObject())
+                                                       return;
+                                               break;
+                                       case Yytoken.TYPE_LEFT_SQUARE:
+                                               status=S_IN_ARRAY;
+                                               statusStack.addFirst(new Integer(status));
+                                               if(!contentHandler.startArray())
+                                                       return;
+                                               break;
+                                       default:
+                                               status=S_IN_ERROR;
+                                       }//inner switch
+                                       break;
+                                       
+                               case S_END:
+                                       return;
+                                       
+                               case S_IN_ERROR:
+                                       throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
+                               }//switch
+                               if(status==S_IN_ERROR){
+                                       throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
+                               }
+                       }while(token.type!=Yytoken.TYPE_EOF);
+               }
+               catch(IOException ie){
+                       status = S_IN_ERROR;
+                       throw ie;
+               }
+               catch(ParseException pe){
+                       status = S_IN_ERROR;
+                       throw pe;
+               }
+               catch(RuntimeException re){
+                       status = S_IN_ERROR;
+                       throw re;
+               }
+               catch(Error e){
+                       status = S_IN_ERROR;
+                       throw e;
+               }
+               
+               status = S_IN_ERROR;
+               throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
+       }
+}
diff --git a/src/org/json/simple/parser/ParseException.java b/src/org/json/simple/parser/ParseException.java
new file mode 100644 (file)
index 0000000..09b1fd4
--- /dev/null
@@ -0,0 +1,90 @@
+package org.json.simple.parser;
+
+/**
+ * ParseException explains why and where the error occurs in source JSON text.
+ * 
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ *
+ */
+public class ParseException extends Exception {
+       private static final long serialVersionUID = -7880698968187728547L;
+       
+       public static final int ERROR_UNEXPECTED_CHAR = 0;
+       public static final int ERROR_UNEXPECTED_TOKEN = 1;
+       public static final int ERROR_UNEXPECTED_EXCEPTION = 2;
+
+       private int errorType;
+       private Object unexpectedObject;
+       private int position;
+       
+       public ParseException(int errorType){
+               this(-1, errorType, null);
+       }
+       
+       public ParseException(int errorType, Object unexpectedObject){
+               this(-1, errorType, unexpectedObject);
+       }
+       
+       public ParseException(int position, int errorType, Object unexpectedObject){
+               this.position = position;
+               this.errorType = errorType;
+               this.unexpectedObject = unexpectedObject;
+       }
+       
+       public int getErrorType() {
+               return errorType;
+       }
+       
+       public void setErrorType(int errorType) {
+               this.errorType = errorType;
+       }
+       
+       /**
+        * @see org.json.simple.parser.JSONParser#getPosition()
+        * 
+        * @return The character position (starting with 0) of the input where the error occurs.
+        */
+       public int getPosition() {
+               return position;
+       }
+       
+       public void setPosition(int position) {
+               this.position = position;
+       }
+       
+       /**
+        * @see org.json.simple.parser.Yytoken
+        * 
+        * @return One of the following base on the value of errorType:
+        *                      ERROR_UNEXPECTED_CHAR           java.lang.Character
+        *                      ERROR_UNEXPECTED_TOKEN          org.json.simple.parser.Yytoken
+        *                      ERROR_UNEXPECTED_EXCEPTION      java.lang.Exception
+        */
+       public Object getUnexpectedObject() {
+               return unexpectedObject;
+       }
+       
+       public void setUnexpectedObject(Object unexpectedObject) {
+               this.unexpectedObject = unexpectedObject;
+       }
+       
+       public String getMessage() {
+               StringBuffer sb = new StringBuffer();
+               
+               switch(errorType){
+               case ERROR_UNEXPECTED_CHAR:
+                       sb.append("Unexpected character (").append(unexpectedObject).append(") at position ").append(position).append(".");
+                       break;
+               case ERROR_UNEXPECTED_TOKEN:
+                       sb.append("Unexpected token ").append(unexpectedObject).append(" at position ").append(position).append(".");
+                       break;
+               case ERROR_UNEXPECTED_EXCEPTION:
+                       sb.append("Unexpected exception at position ").append(position).append(": ").append(unexpectedObject);
+                       break;
+               default:
+                       sb.append("Unkown error at position ").append(position).append(".");
+                       break;
+               }
+               return sb.toString();
+       }
+}
diff --git a/src/org/json/simple/parser/Yylex.java b/src/org/json/simple/parser/Yylex.java
new file mode 100644 (file)
index 0000000..dc36fa2
--- /dev/null
@@ -0,0 +1,688 @@
+/* The following code was generated by JFlex 1.4.2 */
+
+package org.json.simple.parser;
+
+class Yylex {
+
+  /** This character denotes the end of file */
+  public static final int YYEOF = -1;
+
+  /** initial size of the lookahead buffer */
+  private static final int ZZ_BUFFERSIZE = 16384;
+
+  /** lexical states */
+  public static final int YYINITIAL = 0;
+  public static final int STRING_BEGIN = 2;
+
+  /**
+   * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l
+   * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l
+   *                  at the beginning of a line
+   * l is of the form l = 2*k, k a non negative integer
+   */
+  private static final int ZZ_LEXSTATE[] = { 
+     0,  0,  1, 1
+  };
+
+  /** 
+   * Translates characters to character classes
+   */
+  private static final String ZZ_CMAP_PACKED = 
+    "\11\0\1\7\1\7\2\0\1\7\22\0\1\7\1\0\1\11\10\0"+
+    "\1\6\1\31\1\2\1\4\1\12\12\3\1\32\6\0\4\1\1\5"+
+    "\1\1\24\0\1\27\1\10\1\30\3\0\1\22\1\13\2\1\1\21"+
+    "\1\14\5\0\1\23\1\0\1\15\3\0\1\16\1\24\1\17\1\20"+
+    "\5\0\1\25\1\0\1\26\uff82\0";
+
+  /** 
+   * Translates characters to character classes
+   */
+  private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED);
+
+  /** 
+   * Translates DFA states to action switch labels.
+   */
+  private static final int [] ZZ_ACTION = zzUnpackAction();
+
+  private static final String ZZ_ACTION_PACKED_0 =
+    "\2\0\2\1\1\2\1\3\1\4\3\1\1\5\1\6"+
+    "\1\7\1\10\1\11\1\12\1\13\1\14\1\15\5\0"+
+    "\1\14\1\16\1\17\1\20\1\21\1\22\1\23\1\24"+
+    "\1\0\1\25\1\0\1\25\4\0\1\26\1\27\2\0"+
+    "\1\30";
+
+  private static int [] zzUnpackAction() {
+    int [] result = new int[45];
+    int offset = 0;
+    offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackAction(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+
+  /** 
+   * Translates a state to a row index in the transition table
+   */
+  private static final int [] ZZ_ROWMAP = zzUnpackRowMap();
+
+  private static final String ZZ_ROWMAP_PACKED_0 =
+    "\0\0\0\33\0\66\0\121\0\154\0\207\0\66\0\242"+
+    "\0\275\0\330\0\66\0\66\0\66\0\66\0\66\0\66"+
+    "\0\363\0\u010e\0\66\0\u0129\0\u0144\0\u015f\0\u017a\0\u0195"+
+    "\0\66\0\66\0\66\0\66\0\66\0\66\0\66\0\66"+
+    "\0\u01b0\0\u01cb\0\u01e6\0\u01e6\0\u0201\0\u021c\0\u0237\0\u0252"+
+    "\0\66\0\66\0\u026d\0\u0288\0\66";
+
+  private static int [] zzUnpackRowMap() {
+    int [] result = new int[45];
+    int offset = 0;
+    offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackRowMap(String packed, int offset, int [] result) {
+    int i = 0;  /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int high = packed.charAt(i++) << 16;
+      result[j++] = high | packed.charAt(i++);
+    }
+    return j;
+  }
+
+  /** 
+   * The transition table of the DFA
+   */
+  private static final int ZZ_TRANS [] = {
+    2, 2, 3, 4, 2, 2, 2, 5, 2, 6, 
+    2, 2, 7, 8, 2, 9, 2, 2, 2, 2, 
+    2, 10, 11, 12, 13, 14, 15, 16, 16, 16, 
+    16, 16, 16, 16, 16, 17, 18, 16, 16, 16, 
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 
+    16, 16, 16, 16, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, 4, 19, 20, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, 20, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    21, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    23, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, 16, 16, 16, 16, 16, 16, 16, 
+    16, -1, -1, 16, 16, 16, 16, 16, 16, 16, 
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 
+    -1, -1, -1, -1, -1, -1, -1, -1, 24, 25, 
+    26, 27, 28, 29, 30, 31, 32, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    33, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, 34, 35, -1, -1, 
+    34, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    36, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, 37, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, 38, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, 39, -1, 39, -1, 39, -1, -1, 
+    -1, -1, -1, 39, 39, -1, -1, -1, -1, 39, 
+    39, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, 33, -1, 20, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, 20, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, 35, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, 38, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, 40, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, 41, -1, -1, -1, -1, -1, 
+    -1, -1, -1, -1, -1, 42, -1, 42, -1, 42, 
+    -1, -1, -1, -1, -1, 42, 42, -1, -1, -1, 
+    -1, 42, 42, -1, -1, -1, -1, -1, -1, -1, 
+    -1, -1, 43, -1, 43, -1, 43, -1, -1, -1, 
+    -1, -1, 43, 43, -1, -1, -1, -1, 43, 43, 
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, 44, 
+    -1, 44, -1, 44, -1, -1, -1, -1, -1, 44, 
+    44, -1, -1, -1, -1, 44, 44, -1, -1, -1, 
+    -1, -1, -1, -1, -1, 
+  };
+
+  /* error codes */
+  private static final int ZZ_UNKNOWN_ERROR = 0;
+  private static final int ZZ_NO_MATCH = 1;
+  private static final int ZZ_PUSHBACK_2BIG = 2;
+
+  /* error messages for the codes above */
+  private static final String ZZ_ERROR_MSG[] = {
+    "Unkown internal scanner error",
+    "Error: could not match input",
+    "Error: pushback value was too large"
+  };
+
+  /**
+   * ZZ_ATTRIBUTE[aState] contains the attributes of state <code>aState</code>
+   */
+  private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
+
+  private static final String ZZ_ATTRIBUTE_PACKED_0 =
+    "\2\0\1\11\3\1\1\11\3\1\6\11\2\1\1\11"+
+    "\5\0\10\11\1\0\1\1\1\0\1\1\4\0\2\11"+
+    "\2\0\1\11";
+
+  private static int [] zzUnpackAttribute() {
+    int [] result = new int[45];
+    int offset = 0;
+    offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackAttribute(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+  /** the input device */
+  private java.io.Reader zzReader;
+
+  /** the current state of the DFA */
+  private int zzState;
+
+  /** the current lexical state */
+  private int zzLexicalState = YYINITIAL;
+
+  /** this buffer contains the current text to be matched and is
+      the source of the yytext() string */
+  private char zzBuffer[] = new char[ZZ_BUFFERSIZE];
+
+  /** the textposition at the last accepting state */
+  private int zzMarkedPos;
+
+  /** the current text position in the buffer */
+  private int zzCurrentPos;
+
+  /** startRead marks the beginning of the yytext() string in the buffer */
+  private int zzStartRead;
+
+  /** endRead marks the last character in the buffer, that has been read
+      from input */
+  private int zzEndRead;
+
+  /** number of newlines encountered up to the start of the matched text */
+  private int yyline;
+
+  /** the number of characters up to the start of the matched text */
+  private int yychar;
+
+  /**
+   * the number of characters from the last newline up to the start of the 
+   * matched text
+   */
+  private int yycolumn;
+
+  /** 
+   * zzAtBOL == true <=> the scanner is currently at the beginning of a line
+   */
+  private boolean zzAtBOL = true;
+
+  /** zzAtEOF == true <=> the scanner is at the EOF */
+  private boolean zzAtEOF;
+
+  /* user code: */
+private StringBuffer sb=new StringBuffer();
+
+int getPosition(){
+       return yychar;
+}
+
+
+
+  /**
+   * Creates a new scanner
+   * There is also a java.io.InputStream version of this constructor.
+   *
+   * @param   in  the java.io.Reader to read input from.
+   */
+  Yylex(java.io.Reader in) {
+    this.zzReader = in;
+  }
+
+  /**
+   * Creates a new scanner.
+   * There is also java.io.Reader version of this constructor.
+   *
+   * @param   in  the java.io.Inputstream to read input from.
+   */
+  Yylex(java.io.InputStream in) {
+    this(new java.io.InputStreamReader(in));
+  }
+
+  /** 
+   * Unpacks the compressed character translation table.
+   *
+   * @param packed   the packed character translation table
+   * @return         the unpacked character translation table
+   */
+  private static char [] zzUnpackCMap(String packed) {
+    char [] map = new char[0x10000];
+    int i = 0;  /* index in packed string  */
+    int j = 0;  /* index in unpacked array */
+    while (i < 90) {
+      int  count = packed.charAt(i++);
+      char value = packed.charAt(i++);
+      do map[j++] = value; while (--count > 0);
+    }
+    return map;
+  }
+
+
+  /**
+   * Refills the input buffer.
+   *
+   * @return      <code>false</code>, iff there was new input.
+   * 
+   * @exception   java.io.IOException  if any I/O-Error occurs
+   */
+  private boolean zzRefill() throws java.io.IOException {
+
+    /* first: make room (if you can) */
+    if (zzStartRead > 0) {
+      System.arraycopy(zzBuffer, zzStartRead,
+                       zzBuffer, 0,
+                       zzEndRead-zzStartRead);
+
+      /* translate stored positions */
+      zzEndRead-= zzStartRead;
+      zzCurrentPos-= zzStartRead;
+      zzMarkedPos-= zzStartRead;
+      zzStartRead = 0;
+    }
+
+    /* is the buffer big enough? */
+    if (zzCurrentPos >= zzBuffer.length) {
+      /* if not: blow it up */
+      char newBuffer[] = new char[zzCurrentPos*2];
+      System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length);
+      zzBuffer = newBuffer;
+    }
+
+    /* finally: fill the buffer with new input */
+    int numRead = zzReader.read(zzBuffer, zzEndRead,
+                                            zzBuffer.length-zzEndRead);
+
+    if (numRead > 0) {
+      zzEndRead+= numRead;
+      return false;
+    }
+    // unlikely but not impossible: read 0 characters, but not at end of stream    
+    if (numRead == 0) {
+      int c = zzReader.read();
+      if (c == -1) {
+        return true;
+      } else {
+        zzBuffer[zzEndRead++] = (char) c;
+        return false;
+      }     
+    }
+
+       // numRead < 0
+    return true;
+  }
+
+    
+  /**
+   * Closes the input stream.
+   */
+  public final void yyclose() throws java.io.IOException {
+    zzAtEOF = true;            /* indicate end of file */
+    zzEndRead = zzStartRead;  /* invalidate buffer    */
+
+    if (zzReader != null)
+      zzReader.close();
+  }
+
+
+  /**
+   * Resets the scanner to read from a new input stream.
+   * Does not close the old reader.
+   *
+   * All internal variables are reset, the old input stream 
+   * <b>cannot</b> be reused (internal buffer is discarded and lost).
+   * Lexical state is set to <tt>ZZ_INITIAL</tt>.
+   *
+   * @param reader   the new input stream 
+   */
+  public final void yyreset(java.io.Reader reader) {
+    zzReader = reader;
+    zzAtBOL  = true;
+    zzAtEOF  = false;
+    zzEndRead = zzStartRead = 0;
+    zzCurrentPos = zzMarkedPos = 0;
+    yyline = yychar = yycolumn = 0;
+    zzLexicalState = YYINITIAL;
+  }
+
+
+  /**
+   * Returns the current lexical state.
+   */
+  public final int yystate() {
+    return zzLexicalState;
+  }
+
+
+  /**
+   * Enters a new lexical state
+   *
+   * @param newState the new lexical state
+   */
+  public final void yybegin(int newState) {
+    zzLexicalState = newState;
+  }
+
+
+  /**
+   * Returns the text matched by the current regular expression.
+   */
+  public final String yytext() {
+    return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
+  }
+
+
+  /**
+   * Returns the character at position <tt>pos</tt> from the 
+   * matched text. 
+   * 
+   * It is equivalent to yytext().charAt(pos), but faster
+   *
+   * @param pos the position of the character to fetch. 
+   *            A value from 0 to yylength()-1.
+   *
+   * @return the character at position pos
+   */
+  public final char yycharat(int pos) {
+    return zzBuffer[zzStartRead+pos];
+  }
+
+
+  /**
+   * Returns the length of the matched text region.
+   */
+  public final int yylength() {
+    return zzMarkedPos-zzStartRead;
+  }
+
+
+  /**
+   * Reports an error that occured while scanning.
+   *
+   * In a wellformed scanner (no or only correct usage of 
+   * yypushback(int) and a match-all fallback rule) this method 
+   * will only be called with things that "Can't Possibly Happen".
+   * If this method is called, something is seriously wrong
+   * (e.g. a JFlex bug producing a faulty scanner etc.).
+   *
+   * Usual syntax/scanner level error handling should be done
+   * in error fallback rules.
+   *
+   * @param   errorCode  the code of the errormessage to display
+   */
+  private void zzScanError(int errorCode) {
+    String message;
+    try {
+      message = ZZ_ERROR_MSG[errorCode];
+    }
+    catch (ArrayIndexOutOfBoundsException e) {
+      message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
+    }
+
+    throw new Error(message);
+  } 
+
+
+  /**
+   * Pushes the specified amount of characters back into the input stream.
+   *
+   * They will be read again by then next call of the scanning method
+   *
+   * @param number  the number of characters to be read again.
+   *                This number must not be greater than yylength()!
+   */
+  public void yypushback(int number)  {
+    if ( number > yylength() )
+      zzScanError(ZZ_PUSHBACK_2BIG);
+
+    zzMarkedPos -= number;
+  }
+
+
+  /**
+   * Resumes scanning until the next regular expression is matched,
+   * the end of input is encountered or an I/O-Error occurs.
+   *
+   * @return      the next token
+   * @exception   java.io.IOException  if any I/O-Error occurs
+   */
+  public Yytoken yylex() throws java.io.IOException, ParseException {
+    int zzInput;
+    int zzAction;
+
+    // cached fields:
+    int zzCurrentPosL;
+    int zzMarkedPosL;
+    int zzEndReadL = zzEndRead;
+    char [] zzBufferL = zzBuffer;
+    char [] zzCMapL = ZZ_CMAP;
+
+    int [] zzTransL = ZZ_TRANS;
+    int [] zzRowMapL = ZZ_ROWMAP;
+    int [] zzAttrL = ZZ_ATTRIBUTE;
+
+    while (true) {
+      zzMarkedPosL = zzMarkedPos;
+
+      yychar+= zzMarkedPosL-zzStartRead;
+
+      zzAction = -1;
+
+      zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
+  
+      zzState = ZZ_LEXSTATE[zzLexicalState];
+
+
+      zzForAction: {
+        while (true) {
+    
+          if (zzCurrentPosL < zzEndReadL)
+            zzInput = zzBufferL[zzCurrentPosL++];
+          else if (zzAtEOF) {
+            zzInput = YYEOF;
+            break zzForAction;
+          }
+          else {
+            // store back cached positions
+            zzCurrentPos  = zzCurrentPosL;
+            zzMarkedPos   = zzMarkedPosL;
+            boolean eof = zzRefill();
+            // get translated positions and possibly new buffer
+            zzCurrentPosL  = zzCurrentPos;
+            zzMarkedPosL   = zzMarkedPos;
+            zzBufferL      = zzBuffer;
+            zzEndReadL     = zzEndRead;
+            if (eof) {
+              zzInput = YYEOF;
+              break zzForAction;
+            }
+            else {
+              zzInput = zzBufferL[zzCurrentPosL++];
+            }
+          }
+          int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ];
+          if (zzNext == -1) break zzForAction;
+          zzState = zzNext;
+
+          int zzAttributes = zzAttrL[zzState];
+          if ( (zzAttributes & 1) == 1 ) {
+            zzAction = zzState;
+            zzMarkedPosL = zzCurrentPosL;
+            if ( (zzAttributes & 8) == 8 ) break zzForAction;
+          }
+
+        }
+      }
+
+      // store back cached position
+      zzMarkedPos = zzMarkedPosL;
+
+      switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
+        case 11: 
+          { sb.append(yytext());
+          }
+        case 25: break;
+        case 4: 
+          { sb = null; sb = new StringBuffer(); yybegin(STRING_BEGIN);
+          }
+        case 26: break;
+        case 16: 
+          { sb.append('\b');
+          }
+        case 27: break;
+        case 6: 
+          { return new Yytoken(Yytoken.TYPE_RIGHT_BRACE,null);
+          }
+        case 28: break;
+        case 23: 
+          { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val);
+          }
+        case 29: break;
+        case 22: 
+          { return new Yytoken(Yytoken.TYPE_VALUE, null);
+          }
+        case 30: break;
+        case 13: 
+          { yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE, sb.toString());
+          }
+        case 31: break;
+        case 12: 
+          { sb.append('\\');
+          }
+        case 32: break;
+        case 21: 
+          { Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val);
+          }
+        case 33: break;
+        case 1: 
+          { throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_CHAR, new Character(yycharat(0)));
+          }
+        case 34: break;
+        case 8: 
+          { return new Yytoken(Yytoken.TYPE_RIGHT_SQUARE,null);
+          }
+        case 35: break;
+        case 19: 
+          { sb.append('\r');
+          }
+        case 36: break;
+        case 15: 
+          { sb.append('/');
+          }
+        case 37: break;
+        case 10: 
+          { return new Yytoken(Yytoken.TYPE_COLON,null);
+          }
+        case 38: break;
+        case 14: 
+          { sb.append('"');
+          }
+        case 39: break;
+        case 5: 
+          { return new Yytoken(Yytoken.TYPE_LEFT_BRACE,null);
+          }
+        case 40: break;
+        case 17: 
+          { sb.append('\f');
+          }
+        case 41: break;
+        case 24: 
+          { try{
+                                                                                                               int ch=Integer.parseInt(yytext().substring(2),16);
+                                                                                                               sb.append((char)ch);
+                                                                                                       }
+                                                                                                       catch(Exception e){
+                                                                                                               throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_EXCEPTION, e);
+                                                                                                       }
+          }
+        case 42: break;
+        case 20: 
+          { sb.append('\t');
+          }
+        case 43: break;
+        case 7: 
+          { return new Yytoken(Yytoken.TYPE_LEFT_SQUARE,null);
+          }
+        case 44: break;
+        case 2: 
+          { Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val);
+          }
+        case 45: break;
+        case 18: 
+          { sb.append('\n');
+          }
+        case 46: break;
+        case 9: 
+          { return new Yytoken(Yytoken.TYPE_COMMA,null);
+          }
+        case 47: break;
+        case 3: 
+          { 
+          }
+        case 48: break;
+        default: 
+          if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
+            zzAtEOF = true;
+            return null;
+          } 
+          else {
+            zzScanError(ZZ_NO_MATCH);
+          }
+      }
+    }
+  }
+
+
+}
diff --git a/src/org/json/simple/parser/Yytoken.java b/src/org/json/simple/parser/Yytoken.java
new file mode 100644 (file)
index 0000000..9d7e7e7
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * $Id: Yytoken.java,v 1.1 2006/04/15 14:10:48 platform Exp $
+ * Created on 2006-4-15
+ */
+package org.json.simple.parser;
+
+/**
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public class Yytoken {
+       public static final int TYPE_VALUE=0;//JSON primitive value: string,number,boolean,null
+       public static final int TYPE_LEFT_BRACE=1;
+       public static final int TYPE_RIGHT_BRACE=2;
+       public static final int TYPE_LEFT_SQUARE=3;
+       public static final int TYPE_RIGHT_SQUARE=4;
+       public static final int TYPE_COMMA=5;
+       public static final int TYPE_COLON=6;
+       public static final int TYPE_EOF=-1;//end of file
+       
+       public int type=0;
+       public Object value=null;
+       
+       public Yytoken(int type,Object value){
+               this.type=type;
+               this.value=value;
+       }
+       
+       public String toString(){
+               StringBuffer sb = new StringBuffer();
+               switch(type){
+               case TYPE_VALUE:
+                       sb.append("VALUE(").append(value).append(")");
+                       break;
+               case TYPE_LEFT_BRACE:
+                       sb.append("LEFT BRACE({)");
+                       break;
+               case TYPE_RIGHT_BRACE:
+                       sb.append("RIGHT BRACE(})");
+                       break;
+               case TYPE_LEFT_SQUARE:
+                       sb.append("LEFT SQUARE([)");
+                       break;
+               case TYPE_RIGHT_SQUARE:
+                       sb.append("RIGHT SQUARE(])");
+                       break;
+               case TYPE_COMMA:
+                       sb.append("COMMA(,)");
+                       break;
+               case TYPE_COLON:
+                       sb.append("COLON(:)");
+                       break;
+               case TYPE_EOF:
+                       sb.append("END OF FILE");
+                       break;
+               }
+               return sb.toString();
+       }
+}
diff --git a/src/org/xml/sax/AttributeList.java b/src/org/xml/sax/AttributeList.java
new file mode 100644 (file)
index 0000000..9285eac
--- /dev/null
@@ -0,0 +1,193 @@
+// SAX Attribute List Interface.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: AttributeList.java,v 1.7 2004/04/26 17:34:34 dmegginson Exp $
+
+package org.xml.sax;
+
+/**
+ * Interface for an element's attribute specifications.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This is the original SAX1 interface for reporting an element's
+ * attributes.  Unlike the new {@link org.xml.sax.Attributes Attributes}
+ * interface, it does not support Namespace-related information.</p>
+ *
+ * <p>When an attribute list is supplied as part of a
+ * {@link org.xml.sax.DocumentHandler#startElement startElement}
+ * event, the list will return valid results only during the
+ * scope of the event; once the event handler returns control
+ * to the parser, the attribute list is invalid.  To save a
+ * persistent copy of the attribute list, use the SAX1
+ * {@link org.xml.sax.helpers.AttributeListImpl AttributeListImpl}
+ * helper class.</p>
+ *
+ * <p>An attribute list includes only attributes that have been
+ * specified or defaulted: #IMPLIED attributes will not be included.</p>
+ *
+ * <p>There are two ways for the SAX application to obtain information
+ * from the AttributeList.  First, it can iterate through the entire
+ * list:</p>
+ *
+ * <pre>
+ * public void startElement (String name, AttributeList atts) {
+ *   for (int i = 0; i < atts.getLength(); i++) {
+ *     String name = atts.getName(i);
+ *     String type = atts.getType(i);
+ *     String value = atts.getValue(i);
+ *     [...]
+ *   }
+ * }
+ * </pre>
+ *
+ * <p>(Note that the result of getLength() will be zero if there
+ * are no attributes.)
+ *
+ * <p>As an alternative, the application can request the value or
+ * type of specific attributes:</p>
+ *
+ * <pre>
+ * public void startElement (String name, AttributeList atts) {
+ *   String identifier = atts.getValue("id");
+ *   String label = atts.getValue("label");
+ *   [...]
+ * }
+ * </pre>
+ *
+ * @deprecated This interface has been replaced by the SAX2
+ *             {@link org.xml.sax.Attributes Attributes}
+ *             interface, which includes Namespace support.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.DocumentHandler#startElement startElement
+ * @see org.xml.sax.helpers.AttributeListImpl AttributeListImpl
+ */
+public interface AttributeList {
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Iteration methods.
+    ////////////////////////////////////////////////////////////////////
+    
+
+    /**
+     * Return the number of attributes in this list.
+     *
+     * <p>The SAX parser may provide attributes in any
+     * arbitrary order, regardless of the order in which they were
+     * declared or specified.  The number of attributes may be
+     * zero.</p>
+     *
+     * @return The number of attributes in the list.  
+     */
+    public abstract int getLength ();
+    
+    
+    /**
+     * Return the name of an attribute in this list (by position).
+     *
+     * <p>The names must be unique: the SAX parser shall not include the
+     * same attribute twice.  Attributes without values (those declared
+     * #IMPLIED without a value specified in the start tag) will be
+     * omitted from the list.</p>
+     *
+     * <p>If the attribute name has a namespace prefix, the prefix
+     * will still be attached.</p>
+     *
+     * @param i The index of the attribute in the list (starting at 0).
+     * @return The name of the indexed attribute, or null
+     *         if the index is out of range.
+     * @see #getLength 
+     */
+    public abstract String getName (int i);
+    
+    
+    /**
+     * Return the type of an attribute in the list (by position).
+     *
+     * <p>The attribute type is one of the strings "CDATA", "ID",
+     * "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES",
+     * or "NOTATION" (always in upper case).</p>
+     *
+     * <p>If the parser has not read a declaration for the attribute,
+     * or if the parser does not report attribute types, then it must
+     * return the value "CDATA" as stated in the XML 1.0 Recommentation
+     * (clause 3.3.3, "Attribute-Value Normalization").</p>
+     *
+     * <p>For an enumerated attribute that is not a notation, the
+     * parser will report the type as "NMTOKEN".</p>
+     *
+     * @param i The index of the attribute in the list (starting at 0).
+     * @return The attribute type as a string, or
+     *         null if the index is out of range.
+     * @see #getLength 
+     * @see #getType(java.lang.String)
+     */
+    public abstract String getType (int i);
+    
+    
+    /**
+     * Return the value of an attribute in the list (by position).
+     *
+     * <p>If the attribute value is a list of tokens (IDREFS,
+     * ENTITIES, or NMTOKENS), the tokens will be concatenated
+     * into a single string separated by whitespace.</p>
+     *
+     * @param i The index of the attribute in the list (starting at 0).
+     * @return The attribute value as a string, or
+     *         null if the index is out of range.
+     * @see #getLength
+     * @see #getValue(java.lang.String)
+     */
+    public abstract String getValue (int i);
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Lookup methods.
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Return the type of an attribute in the list (by name).
+     *
+     * <p>The return value is the same as the return value for
+     * getType(int).</p>
+     *
+     * <p>If the attribute name has a namespace prefix in the document,
+     * the application must include the prefix here.</p>
+     *
+     * @param name The name of the attribute.
+     * @return The attribute type as a string, or null if no
+     *         such attribute exists.
+     * @see #getType(int)
+     */
+    public abstract String getType (String name);
+    
+    
+    /**
+     * Return the value of an attribute in the list (by name).
+     *
+     * <p>The return value is the same as the return value for
+     * getValue(int).</p>
+     *
+     * <p>If the attribute name has a namespace prefix in the document,
+     * the application must include the prefix here.</p>
+     *
+     * @param name the name of the attribute to return
+     * @return The attribute value as a string, or null if
+     *         no such attribute exists.
+     * @see #getValue(int)
+     */
+    public abstract String getValue (String name);
+    
+}
+
+// end of AttributeList.java
diff --git a/src/org/xml/sax/Attributes.java b/src/org/xml/sax/Attributes.java
new file mode 100644 (file)
index 0000000..b25432d
--- /dev/null
@@ -0,0 +1,257 @@
+// Attributes.java - attribute list with Namespace support
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the public domain.
+// $Id: Attributes.java,v 1.13 2004/03/18 12:28:05 dmegginson Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Interface for a list of XML attributes.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This interface allows access to a list of attributes in
+ * three different ways:</p>
+ *
+ * <ol>
+ * <li>by attribute index;</li>
+ * <li>by Namespace-qualified name; or</li>
+ * <li>by qualified (prefixed) name.</li>
+ * </ol>
+ *
+ * <p>The list will not contain attributes that were declared
+ * #IMPLIED but not specified in the start tag.  It will also not
+ * contain attributes used as Namespace declarations (xmlns*) unless
+ * the <code>http://xml.org/sax/features/namespace-prefixes</code> 
+ * feature is set to <var>true</var> (it is <var>false</var> by 
+ * default).
+ * Because SAX2 conforms to the original "Namespaces in XML"
+ * recommendation, it normally does not
+ * give namespace declaration attributes a namespace URI.
+ * </p>
+ *
+ * <p>Some SAX2 parsers may support using an optional feature flag
+ * (<code>http://xml.org/sax/features/xmlns-uris</code>) to request
+ * that those attributes be given URIs, conforming to a later
+ * backwards-incompatible revision of that recommendation.  (The
+ * attribute's "local name" will be the prefix, or "xmlns" when
+ * defining a default element namespace.)  For portability, handler
+ * code should always resolve that conflict, rather than requiring
+ * parsers that can change the setting of that feature flag.  </p>
+ *
+ * <p>If the namespace-prefixes feature (see above) is
+ * <var>false</var>, access by qualified name may not be available; if
+ * the <code>http://xml.org/sax/features/namespaces</code> feature is
+ * <var>false</var>, access by Namespace-qualified names may not be
+ * available.</p>
+ *
+ * <p>This interface replaces the now-deprecated SAX1 {@link
+ * org.xml.sax.AttributeList AttributeList} interface, which does not 
+ * contain Namespace support.  In addition to Namespace support, it 
+ * adds the <var>getIndex</var> methods (below).</p>
+ *
+ * <p>The order of attributes in the list is unspecified, and will
+ * vary from implementation to implementation.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.helpers.AttributesImpl
+ * @see org.xml.sax.ext.DeclHandler#attributeDecl
+ */
+public interface Attributes
+{
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Indexed access.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Return the number of attributes in the list.
+     *
+     * <p>Once you know the number of attributes, you can iterate
+     * through the list.</p>
+     *
+     * @return The number of attributes in the list.
+     * @see #getURI(int)
+     * @see #getLocalName(int)
+     * @see #getQName(int)
+     * @see #getType(int)
+     * @see #getValue(int)
+     */
+    public abstract int getLength ();
+
+
+    /**
+     * Look up an attribute's Namespace URI by index.
+     *
+     * @param index The attribute index (zero-based).
+     * @return The Namespace URI, or the empty string if none
+     *         is available, or null if the index is out of
+     *         range.
+     * @see #getLength
+     */
+    public abstract String getURI (int index);
+
+
+    /**
+     * Look up an attribute's local name by index.
+     *
+     * @param index The attribute index (zero-based).
+     * @return The local name, or the empty string if Namespace
+     *         processing is not being performed, or null
+     *         if the index is out of range.
+     * @see #getLength
+     */
+    public abstract String getLocalName (int index);
+
+
+    /**
+     * Look up an attribute's XML qualified (prefixed) name by index.
+     *
+     * @param index The attribute index (zero-based).
+     * @return The XML qualified name, or the empty string
+     *         if none is available, or null if the index
+     *         is out of range.
+     * @see #getLength
+     */
+    public abstract String getQName (int index);
+
+
+    /**
+     * Look up an attribute's type by index.
+     *
+     * <p>The attribute type is one of the strings "CDATA", "ID",
+     * "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES",
+     * or "NOTATION" (always in upper case).</p>
+     *
+     * <p>If the parser has not read a declaration for the attribute,
+     * or if the parser does not report attribute types, then it must
+     * return the value "CDATA" as stated in the XML 1.0 Recommendation
+     * (clause 3.3.3, "Attribute-Value Normalization").</p>
+     *
+     * <p>For an enumerated attribute that is not a notation, the
+     * parser will report the type as "NMTOKEN".</p>
+     *
+     * @param index The attribute index (zero-based).
+     * @return The attribute's type as a string, or null if the
+     *         index is out of range.
+     * @see #getLength
+     */
+    public abstract String getType (int index);
+
+
+    /**
+     * Look up an attribute's value by index.
+     *
+     * <p>If the attribute value is a list of tokens (IDREFS,
+     * ENTITIES, or NMTOKENS), the tokens will be concatenated
+     * into a single string with each token separated by a
+     * single space.</p>
+     *
+     * @param index The attribute index (zero-based).
+     * @return The attribute's value as a string, or null if the
+     *         index is out of range.
+     * @see #getLength
+     */
+    public abstract String getValue (int index);
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Name-based query.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Look up the index of an attribute by Namespace name.
+     *
+     * @param uri The Namespace URI, or the empty string if
+     *        the name has no Namespace URI.
+     * @param localName The attribute's local name.
+     * @return The index of the attribute, or -1 if it does not
+     *         appear in the list.
+     */
+    public int getIndex (String uri, String localName);
+
+
+    /**
+     * Look up the index of an attribute by XML qualified (prefixed) name.
+     *
+     * @param qName The qualified (prefixed) name.
+     * @return The index of the attribute, or -1 if it does not
+     *         appear in the list.
+     */
+    public int getIndex (String qName);
+
+
+    /**
+     * Look up an attribute's type by Namespace name.
+     *
+     * <p>See {@link #getType(int) getType(int)} for a description
+     * of the possible types.</p>
+     *
+     * @param uri The Namespace URI, or the empty String if the
+     *        name has no Namespace URI.
+     * @param localName The local name of the attribute.
+     * @return The attribute type as a string, or null if the
+     *         attribute is not in the list or if Namespace
+     *         processing is not being performed.
+     */
+    public abstract String getType (String uri, String localName);
+
+
+    /**
+     * Look up an attribute's type by XML qualified (prefixed) name.
+     *
+     * <p>See {@link #getType(int) getType(int)} for a description
+     * of the possible types.</p>
+     *
+     * @param qName The XML qualified name.
+     * @return The attribute type as a string, or null if the
+     *         attribute is not in the list or if qualified names
+     *         are not available.
+     */
+    public abstract String getType (String qName);
+
+
+    /**
+     * Look up an attribute's value by Namespace name.
+     *
+     * <p>See {@link #getValue(int) getValue(int)} for a description
+     * of the possible values.</p>
+     *
+     * @param uri The Namespace URI, or the empty String if the
+     *        name has no Namespace URI.
+     * @param localName The local name of the attribute.
+     * @return The attribute value as a string, or null if the
+     *         attribute is not in the list.
+     */
+    public abstract String getValue (String uri, String localName);
+
+
+    /**
+     * Look up an attribute's value by XML qualified (prefixed) name.
+     *
+     * <p>See {@link #getValue(int) getValue(int)} for a description
+     * of the possible values.</p>
+     *
+     * @param qName The XML qualified name.
+     * @return The attribute value as a string, or null if the
+     *         attribute is not in the list or if qualified names
+     *         are not available.
+     */
+    public abstract String getValue (String qName);
+
+}
+
+// end of Attributes.java
diff --git a/src/org/xml/sax/ContentHandler.java b/src/org/xml/sax/ContentHandler.java
new file mode 100644 (file)
index 0000000..3d95c5f
--- /dev/null
@@ -0,0 +1,419 @@
+// ContentHandler.java - handle main document content.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the public domain.
+// $Id: ContentHandler.java,v 1.13 2004/04/26 17:50:49 dmegginson Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Receive notification of the logical content of a document.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This is the main interface that most SAX applications
+ * implement: if the application needs to be informed of basic parsing 
+ * events, it implements this interface and registers an instance with 
+ * the SAX parser using the {@link org.xml.sax.XMLReader#setContentHandler 
+ * setContentHandler} method.  The parser uses the instance to report 
+ * basic document-related events like the start and end of elements 
+ * and character data.</p>
+ *
+ * <p>The order of events in this interface is very important, and
+ * mirrors the order of information in the document itself.  For
+ * example, all of an element's content (character data, processing
+ * instructions, and/or subelements) will appear, in order, between
+ * the startElement event and the corresponding endElement event.</p>
+ *
+ * <p>This interface is similar to the now-deprecated SAX 1.0
+ * DocumentHandler interface, but it adds support for Namespaces
+ * and for reporting skipped entities (in non-validating XML
+ * processors).</p>
+ *
+ * <p>Implementors should note that there is also a 
+ * <code>ContentHandler</code> class in the <code>java.net</code>
+ * package; that means that it's probably a bad idea to do</p>
+ *
+ * <pre>import java.net.*;
+ * import org.xml.sax.*;
+ * </pre>
+ *
+ * <p>In fact, "import ...*" is usually a sign of sloppy programming
+ * anyway, so the user should consider this a feature rather than a
+ * bug.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1+ (sax2r3pre1)
+ * @see org.xml.sax.XMLReader
+ * @see org.xml.sax.DTDHandler
+ * @see org.xml.sax.ErrorHandler
+ */
+public interface ContentHandler
+{
+
+    /**
+     * Receive an object for locating the origin of SAX document events.
+     *
+     * <p>SAX parsers are strongly encouraged (though not absolutely
+     * required) to supply a locator: if it does so, it must supply
+     * the locator to the application by invoking this method before
+     * invoking any of the other methods in the ContentHandler
+     * interface.</p>
+     *
+     * <p>The locator allows the application to determine the end
+     * position of any document-related event, even if the parser is
+     * not reporting an error.  Typically, the application will
+     * use this information for reporting its own errors (such as
+     * character content that does not match an application's
+     * business rules).  The information returned by the locator
+     * is probably not sufficient for use with a search engine.</p>
+     *
+     * <p>Note that the locator will return correct information only
+     * during the invocation SAX event callbacks after
+     * {@link #startDocument startDocument} returns and before
+     * {@link #endDocument endDocument} is called.  The
+     * application should not attempt to use it at any other time.</p>
+     *
+     * @param locator an object that can return the location of
+     *                any SAX document event
+     * @see org.xml.sax.Locator
+     */
+    public void setDocumentLocator (Locator locator);
+
+
+    /**
+     * Receive notification of the beginning of a document.
+     *
+     * <p>The SAX parser will invoke this method only once, before any
+     * other event callbacks (except for {@link #setDocumentLocator 
+     * setDocumentLocator}).</p>
+     *
+     * @throws org.xml.sax.SAXException any SAX exception, possibly
+     *            wrapping another exception
+     * @see #endDocument
+     */
+    public void startDocument ()
+       throws SAXException;
+
+
+    /**
+     * Receive notification of the end of a document.
+     *
+     * <p><strong>There is an apparent contradiction between the
+     * documentation for this method and the documentation for {@link
+     * org.xml.sax.ErrorHandler#fatalError}.  Until this ambiguity is
+     * resolved in a future major release, clients should make no
+     * assumptions about whether endDocument() will or will not be
+     * invoked when the parser has reported a fatalError() or thrown
+     * an exception.</strong></p>
+     *
+     * <p>The SAX parser will invoke this method only once, and it will
+     * be the last method invoked during the parse.  The parser shall
+     * not invoke this method until it has either abandoned parsing
+     * (because of an unrecoverable error) or reached the end of
+     * input.</p>
+     *
+     * @throws org.xml.sax.SAXException any SAX exception, possibly
+     *            wrapping another exception
+     * @see #startDocument
+     */
+    public void endDocument()
+       throws SAXException;
+
+
+    /**
+     * Begin the scope of a prefix-URI Namespace mapping.
+     *
+     * <p>The information from this event is not necessary for
+     * normal Namespace processing: the SAX XML reader will 
+     * automatically replace prefixes for element and attribute
+     * names when the <code>http://xml.org/sax/features/namespaces</code>
+     * feature is <var>true</var> (the default).</p>
+     *
+     * <p>There are cases, however, when applications need to
+     * use prefixes in character data or in attribute values,
+     * where they cannot safely be expanded automatically; the
+     * start/endPrefixMapping event supplies the information
+     * to the application to expand prefixes in those contexts
+     * itself, if necessary.</p>
+     *
+     * <p>Note that start/endPrefixMapping events are not
+     * guaranteed to be properly nested relative to each other:
+     * all startPrefixMapping events will occur immediately before the
+     * corresponding {@link #startElement startElement} event, 
+     * and all {@link #endPrefixMapping endPrefixMapping}
+     * events will occur immediately after the corresponding
+     * {@link #endElement endElement} event,
+     * but their order is not otherwise 
+     * guaranteed.</p>
+     *
+     * <p>There should never be start/endPrefixMapping events for the
+     * "xml" prefix, since it is predeclared and immutable.</p>
+     *
+     * @param prefix the Namespace prefix being declared.
+     * An empty string is used for the default element namespace,
+     * which has no prefix.
+     * @param uri the Namespace URI the prefix is mapped to
+     * @throws org.xml.sax.SAXException the client may throw
+     *            an exception during processing
+     * @see #endPrefixMapping
+     * @see #startElement
+     */
+    public void startPrefixMapping (String prefix, String uri)
+       throws SAXException;
+
+
+    /**
+     * End the scope of a prefix-URI mapping.
+     *
+     * <p>See {@link #startPrefixMapping startPrefixMapping} for 
+     * details.  These events will always occur immediately after the
+     * corresponding {@link #endElement endElement} event, but the order of 
+     * {@link #endPrefixMapping endPrefixMapping} events is not otherwise
+     * guaranteed.</p>
+     *
+     * @param prefix the prefix that was being mapped.
+     * This is the empty string when a default mapping scope ends.
+     * @throws org.xml.sax.SAXException the client may throw
+     *            an exception during processing
+     * @see #startPrefixMapping
+     * @see #endElement
+     */
+    public void endPrefixMapping (String prefix)
+       throws SAXException;
+
+
+    /**
+     * Receive notification of the beginning of an element.
+     *
+     * <p>The Parser will invoke this method at the beginning of every
+     * element in the XML document; there will be a corresponding
+     * {@link #endElement endElement} event for every startElement event
+     * (even when the element is empty). All of the element's content will be
+     * reported, in order, before the corresponding endElement
+     * event.</p>
+     *
+     * <p>This event allows up to three name components for each
+     * element:</p>
+     *
+     * <ol>
+     * <li>the Namespace URI;</li>
+     * <li>the local name; and</li>
+     * <li>the qualified (prefixed) name.</li>
+     * </ol>
+     *
+     * <p>Any or all of these may be provided, depending on the
+     * values of the <var>http://xml.org/sax/features/namespaces</var>
+     * and the <var>http://xml.org/sax/features/namespace-prefixes</var>
+     * properties:</p>
+     *
+     * <ul>
+     * <li>the Namespace URI and local name are required when 
+     * the namespaces property is <var>true</var> (the default), and are
+     * optional when the namespaces property is <var>false</var> (if one is
+     * specified, both must be);</li>
+     * <li>the qualified name is required when the namespace-prefixes property
+     * is <var>true</var>, and is optional when the namespace-prefixes property
+     * is <var>false</var> (the default).</li>
+     * </ul>
+     *
+     * <p>Note that the attribute list provided will contain only
+     * attributes with explicit values (specified or defaulted):
+     * #IMPLIED attributes will be omitted.  The attribute list
+     * will contain attributes used for Namespace declarations
+     * (xmlns* attributes) only if the
+     * <code>http://xml.org/sax/features/namespace-prefixes</code>
+     * property is true (it is false by default, and support for a 
+     * true value is optional).</p>
+     *
+     * <p>Like {@link #characters characters()}, attribute values may have
+     * characters that need more than one <code>char</code> value.  </p>
+     *
+     * @param uri the Namespace URI, or the empty string if the
+     *        element has no Namespace URI or if Namespace
+     *        processing is not being performed
+     * @param localName the local name (without prefix), or the
+     *        empty string if Namespace processing is not being
+     *        performed
+     * @param qName the qualified name (with prefix), or the
+     *        empty string if qualified names are not available
+     * @param atts the attributes attached to the element.  If
+     *        there are no attributes, it shall be an empty
+     *        Attributes object.  The value of this object after
+     *        startElement returns is undefined
+     * @throws org.xml.sax.SAXException any SAX exception, possibly
+     *            wrapping another exception
+     * @see #endElement
+     * @see org.xml.sax.Attributes
+     * @see org.xml.sax.helpers.AttributesImpl
+     */
+    public void startElement (String uri, String localName,
+                             String qName, Attributes atts)
+       throws SAXException;
+
+
+    /**
+     * Receive notification of the end of an element.
+     *
+     * <p>The SAX parser will invoke this method at the end of every
+     * element in the XML document; there will be a corresponding
+     * {@link #startElement startElement} event for every endElement 
+     * event (even when the element is empty).</p>
+     *
+     * <p>For information on the names, see startElement.</p>
+     *
+     * @param uri the Namespace URI, or the empty string if the
+     *        element has no Namespace URI or if Namespace
+     *        processing is not being performed
+     * @param localName the local name (without prefix), or the
+     *        empty string if Namespace processing is not being
+     *        performed
+     * @param qName the qualified XML name (with prefix), or the
+     *        empty string if qualified names are not available
+     * @throws org.xml.sax.SAXException any SAX exception, possibly
+     *            wrapping another exception
+     */
+    public void endElement (String uri, String localName,
+                           String qName)
+       throws SAXException;
+
+
+    /**
+     * Receive notification of character data.
+     *
+     * <p>The Parser will call this method to report each chunk of
+     * character data.  SAX parsers may return all contiguous character
+     * data in a single chunk, or they may split it into several
+     * chunks; however, all of the characters in any single event
+     * must come from the same external entity so that the Locator
+     * provides useful information.</p>
+     *
+     * <p>The application must not attempt to read from the array
+     * outside of the specified range.</p>
+     *
+     * <p>Individual characters may consist of more than one Java
+     * <code>char</code> value.  There are two important cases where this
+     * happens, because characters can't be represented in just sixteen bits.
+     * In one case, characters are represented in a <em>Surrogate Pair</em>,
+     * using two special Unicode values. Such characters are in the so-called
+     * "Astral Planes", with a code point above U+FFFF.  A second case involves
+     * composite characters, such as a base character combining with one or
+     * more accent characters. </p>
+     *
+     * <p> Your code should not assume that algorithms using
+     * <code>char</code>-at-a-time idioms will be working in character
+     * units; in some cases they will split characters.  This is relevant
+     * wherever XML permits arbitrary characters, such as attribute values,
+     * processing instruction data, and comments as well as in data reported
+     * from this method.  It's also generally relevant whenever Java code
+     * manipulates internationalized text; the issue isn't unique to XML.</p>
+     *
+     * <p>Note that some parsers will report whitespace in element
+     * content using the {@link #ignorableWhitespace ignorableWhitespace}
+     * method rather than this one (validating parsers <em>must</em> 
+     * do so).</p>
+     *
+     * @param ch the characters from the XML document
+     * @param start the start position in the array
+     * @param length the number of characters to read from the array
+     * @throws org.xml.sax.SAXException any SAX exception, possibly
+     *            wrapping another exception
+     * @see #ignorableWhitespace 
+     * @see org.xml.sax.Locator
+     */
+    public void characters (char ch[], int start, int length)
+       throws SAXException;
+
+
+    /**
+     * Receive notification of ignorable whitespace in element content.
+     *
+     * <p>Validating Parsers must use this method to report each chunk
+     * of whitespace in element content (see the W3C XML 1.0
+     * recommendation, section 2.10): non-validating parsers may also
+     * use this method if they are capable of parsing and using
+     * content models.</p>
+     *
+     * <p>SAX parsers may return all contiguous whitespace in a single
+     * chunk, or they may split it into several chunks; however, all of
+     * the characters in any single event must come from the same
+     * external entity, so that the Locator provides useful
+     * information.</p>
+     *
+     * <p>The application must not attempt to read from the array
+     * outside of the specified range.</p>
+     *
+     * @param ch the characters from the XML document
+     * @param start the start position in the array
+     * @param length the number of characters to read from the array
+     * @throws org.xml.sax.SAXException any SAX exception, possibly
+     *            wrapping another exception
+     * @see #characters
+     */
+    public void ignorableWhitespace (char ch[], int start, int length)
+       throws SAXException;
+
+
+    /**
+     * Receive notification of a processing instruction.
+     *
+     * <p>The Parser will invoke this method once for each processing
+     * instruction found: note that processing instructions may occur
+     * before or after the main document element.</p>
+     *
+     * <p>A SAX parser must never report an XML declaration (XML 1.0,
+     * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
+     * using this method.</p>
+     *
+     * <p>Like {@link #characters characters()}, processing instruction
+     * data may have characters that need more than one <code>char</code>
+     * value. </p>
+     *
+     * @param target the processing instruction target
+     * @param data the processing instruction data, or null if
+     *        none was supplied.  The data does not include any
+     *        whitespace separating it from the target
+     * @throws org.xml.sax.SAXException any SAX exception, possibly
+     *            wrapping another exception
+     */
+    public void processingInstruction (String target, String data)
+       throws SAXException;
+
+
+    /**
+     * Receive notification of a skipped entity.
+     * This is not called for entity references within markup constructs
+     * such as element start tags or markup declarations.  (The XML
+     * recommendation requires reporting skipped external entities.
+     * SAX also reports internal entity expansion/non-expansion, except
+     * within markup constructs.)
+     *
+     * <p>The Parser will invoke this method each time the entity is
+     * skipped.  Non-validating processors may skip entities if they
+     * have not seen the declarations (because, for example, the
+     * entity was declared in an external DTD subset).  All processors
+     * may skip external entities, depending on the values of the
+     * <code>http://xml.org/sax/features/external-general-entities</code>
+     * and the
+     * <code>http://xml.org/sax/features/external-parameter-entities</code>
+     * properties.</p>
+     *
+     * @param name the name of the skipped entity.  If it is a 
+     *        parameter entity, the name will begin with '%', and if
+     *        it is the external DTD subset, it will be the string
+     *        "[dtd]"
+     * @throws org.xml.sax.SAXException any SAX exception, possibly
+     *            wrapping another exception
+     */
+    public void skippedEntity (String name)
+       throws SAXException;
+}
+
+// end of ContentHandler.java
diff --git a/src/org/xml/sax/DTDHandler.java b/src/org/xml/sax/DTDHandler.java
new file mode 100644 (file)
index 0000000..e62222a
--- /dev/null
@@ -0,0 +1,117 @@
+// SAX DTD handler.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: DTDHandler.java,v 1.8 2002/01/30 21:13:43 dbrownell Exp $
+
+package org.xml.sax;
+
+/**
+ * Receive notification of basic DTD-related events.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>If a SAX application needs information about notations and
+ * unparsed entities, then the application implements this 
+ * interface and registers an instance with the SAX parser using 
+ * the parser's setDTDHandler method.  The parser uses the 
+ * instance to report notation and unparsed entity declarations to 
+ * the application.</p>
+ *
+ * <p>Note that this interface includes only those DTD events that
+ * the XML recommendation <em>requires</em> processors to report:
+ * notation and unparsed entity declarations.</p>
+ *
+ * <p>The SAX parser may report these events in any order, regardless
+ * of the order in which the notations and unparsed entities were
+ * declared; however, all DTD events must be reported after the
+ * document handler's startDocument event, and before the first
+ * startElement event.
+ * (If the {@link org.xml.sax.ext.LexicalHandler LexicalHandler} is
+ * used, these events must also be reported before the endDTD event.)
+ * </p>
+ *
+ * <p>It is up to the application to store the information for 
+ * future use (perhaps in a hash table or object tree).
+ * If the application encounters attributes of type "NOTATION",
+ * "ENTITY", or "ENTITIES", it can use the information that it
+ * obtained through this interface to find the entity and/or
+ * notation corresponding with the attribute value.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.XMLReader#setDTDHandler
+ */
+public interface DTDHandler {
+    
+    
+    /**
+     * Receive notification of a notation declaration event.
+     *
+     * <p>It is up to the application to record the notation for later
+     * reference, if necessary;
+     * notations may appear as attribute values and in unparsed entity
+     * declarations, and are sometime used with processing instruction
+     * target names.</p>
+     *
+     * <p>At least one of publicId and systemId must be non-null.
+     * If a system identifier is present, and it is a URL, the SAX
+     * parser must resolve it fully before passing it to the
+     * application through this event.</p>
+     *
+     * <p>There is no guarantee that the notation declaration will be
+     * reported before any unparsed entities that use it.</p>
+     *
+     * @param name The notation name.
+     * @param publicId The notation's public identifier, or null if
+     *        none was given.
+     * @param systemId The notation's system identifier, or null if
+     *        none was given.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #unparsedEntityDecl
+     * @see org.xml.sax.Attributes
+     */
+    public abstract void notationDecl (String name,
+                                      String publicId,
+                                      String systemId)
+       throws SAXException;
+    
+    
+    /**
+     * Receive notification of an unparsed entity declaration event.
+     *
+     * <p>Note that the notation name corresponds to a notation
+     * reported by the {@link #notationDecl notationDecl} event.  
+     * It is up to the application to record the entity for later 
+     * reference, if necessary;
+     * unparsed entities may appear as attribute values. 
+     * </p>
+     *
+     * <p>If the system identifier is a URL, the parser must resolve it
+     * fully before passing it to the application.</p>
+     *
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @param name The unparsed entity's name.
+     * @param publicId The entity's public identifier, or null if none
+     *        was given.
+     * @param systemId The entity's system identifier.
+     * @param notationName The name of the associated notation.
+     * @see #notationDecl
+     * @see org.xml.sax.Attributes
+     */
+    public abstract void unparsedEntityDecl (String name,
+                                            String publicId,
+                                            String systemId,
+                                            String notationName)
+       throws SAXException;
+    
+}
+
+// end of DTDHandler.java
diff --git a/src/org/xml/sax/DocumentHandler.java b/src/org/xml/sax/DocumentHandler.java
new file mode 100644 (file)
index 0000000..d70ae74
--- /dev/null
@@ -0,0 +1,232 @@
+// SAX document handler.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: DocumentHandler.java,v 1.6 2002/01/30 21:13:43 dbrownell Exp $
+
+package org.xml.sax;
+
+/**
+ * Receive notification of general document events.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This was the main event-handling interface for SAX1; in
+ * SAX2, it has been replaced by {@link org.xml.sax.ContentHandler
+ * ContentHandler}, which provides Namespace support and reporting
+ * of skipped entities.  This interface is included in SAX2 only
+ * to support legacy SAX1 applications.</p>
+ *
+ * <p>The order of events in this interface is very important, and
+ * mirrors the order of information in the document itself.  For
+ * example, all of an element's content (character data, processing
+ * instructions, and/or subelements) will appear, in order, between
+ * the startElement event and the corresponding endElement event.</p>
+ *
+ * <p>Application writers who do not want to implement the entire
+ * interface can derive a class from HandlerBase, which implements
+ * the default functionality; parser writers can instantiate
+ * HandlerBase to obtain a default handler.  The application can find
+ * the location of any document event using the Locator interface
+ * supplied by the Parser through the setDocumentLocator method.</p>
+ *
+ * @deprecated This interface has been replaced by the SAX2
+ *             {@link org.xml.sax.ContentHandler ContentHandler}
+ *             interface, which includes Namespace support.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.Parser#setDocumentHandler
+ * @see org.xml.sax.Locator
+ * @see org.xml.sax.HandlerBase
+ */
+public interface DocumentHandler {
+    
+    
+    /**
+     * Receive an object for locating the origin of SAX document events.
+     *
+     * <p>SAX parsers are strongly encouraged (though not absolutely
+     * required) to supply a locator: if it does so, it must supply
+     * the locator to the application by invoking this method before
+     * invoking any of the other methods in the DocumentHandler
+     * interface.</p>
+     *
+     * <p>The locator allows the application to determine the end
+     * position of any document-related event, even if the parser is
+     * not reporting an error.  Typically, the application will
+     * use this information for reporting its own errors (such as
+     * character content that does not match an application's
+     * business rules).  The information returned by the locator
+     * is probably not sufficient for use with a search engine.</p>
+     *
+     * <p>Note that the locator will return correct information only
+     * during the invocation of the events in this interface.  The
+     * application should not attempt to use it at any other time.</p>
+     *
+     * @param locator An object that can return the location of
+     *                any SAX document event.
+     * @see org.xml.sax.Locator
+     */
+    public abstract void setDocumentLocator (Locator locator);
+    
+    
+    /**
+     * Receive notification of the beginning of a document.
+     *
+     * <p>The SAX parser will invoke this method only once, before any
+     * other methods in this interface or in DTDHandler (except for
+     * setDocumentLocator).</p>
+     *
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     */
+    public abstract void startDocument ()
+       throws SAXException;
+    
+    
+    /**
+     * Receive notification of the end of a document.
+     *
+     * <p>The SAX parser will invoke this method only once, and it will
+     * be the last method invoked during the parse.  The parser shall
+     * not invoke this method until it has either abandoned parsing
+     * (because of an unrecoverable error) or reached the end of
+     * input.</p>
+     *
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     */
+    public abstract void endDocument ()
+       throws SAXException;
+    
+    
+    /**
+     * Receive notification of the beginning of an element.
+     *
+     * <p>The Parser will invoke this method at the beginning of every
+     * element in the XML document; there will be a corresponding
+     * endElement() event for every startElement() event (even when the
+     * element is empty). All of the element's content will be
+     * reported, in order, before the corresponding endElement()
+     * event.</p>
+     *
+     * <p>If the element name has a namespace prefix, the prefix will
+     * still be attached.  Note that the attribute list provided will
+     * contain only attributes with explicit values (specified or
+     * defaulted): #IMPLIED attributes will be omitted.</p>
+     *
+     * @param name The element type name.
+     * @param atts The attributes attached to the element, if any.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement
+     * @see org.xml.sax.AttributeList 
+     */
+    public abstract void startElement (String name, AttributeList atts)
+       throws SAXException;
+    
+    
+    /**
+     * Receive notification of the end of an element.
+     *
+     * <p>The SAX parser will invoke this method at the end of every
+     * element in the XML document; there will be a corresponding
+     * startElement() event for every endElement() event (even when the
+     * element is empty).</p>
+     *
+     * <p>If the element name has a namespace prefix, the prefix will
+     * still be attached to the name.</p>
+     *
+     * @param name The element type name
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     */
+    public abstract void endElement (String name)
+       throws SAXException;
+    
+    
+    /**
+     * Receive notification of character data.
+     *
+     * <p>The Parser will call this method to report each chunk of
+     * character data.  SAX parsers may return all contiguous character
+     * data in a single chunk, or they may split it into several
+     * chunks; however, all of the characters in any single event
+     * must come from the same external entity, so that the Locator
+     * provides useful information.</p>
+     *
+     * <p>The application must not attempt to read from the array
+     * outside of the specified range.</p>
+     *
+     * <p>Note that some parsers will report whitespace using the
+     * ignorableWhitespace() method rather than this one (validating
+     * parsers must do so).</p>
+     *
+     * @param ch The characters from the XML document.
+     * @param start The start position in the array.
+     * @param length The number of characters to read from the array.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #ignorableWhitespace 
+     * @see org.xml.sax.Locator
+     */
+    public abstract void characters (char ch[], int start, int length)
+       throws SAXException;
+    
+    
+    /**
+     * Receive notification of ignorable whitespace in element content.
+     *
+     * <p>Validating Parsers must use this method to report each chunk
+     * of ignorable whitespace (see the W3C XML 1.0 recommendation,
+     * section 2.10): non-validating parsers may also use this method
+     * if they are capable of parsing and using content models.</p>
+     *
+     * <p>SAX parsers may return all contiguous whitespace in a single
+     * chunk, or they may split it into several chunks; however, all of
+     * the characters in any single event must come from the same
+     * external entity, so that the Locator provides useful
+     * information.</p>
+     *
+     * <p>The application must not attempt to read from the array
+     * outside of the specified range.</p>
+     *
+     * @param ch The characters from the XML document.
+     * @param start The start position in the array.
+     * @param length The number of characters to read from the array.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #characters
+     */
+    public abstract void ignorableWhitespace (char ch[], int start, int length)
+       throws SAXException;
+    
+    
+    /**
+     * Receive notification of a processing instruction.
+     *
+     * <p>The Parser will invoke this method once for each processing
+     * instruction found: note that processing instructions may occur
+     * before or after the main document element.</p>
+     *
+     * <p>A SAX parser should never report an XML declaration (XML 1.0,
+     * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
+     * using this method.</p>
+     *
+     * @param target The processing instruction target.
+     * @param data The processing instruction data, or null if
+     *        none was supplied.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     */
+    public abstract void processingInstruction (String target, String data)
+       throws SAXException;
+    
+}
+
+// end of DocumentHandler.java
diff --git a/src/org/xml/sax/EntityResolver.java b/src/org/xml/sax/EntityResolver.java
new file mode 100644 (file)
index 0000000..65b04eb
--- /dev/null
@@ -0,0 +1,119 @@
+// SAX entity resolver.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: EntityResolver.java,v 1.10 2002/01/30 21:13:44 dbrownell Exp $
+
+package org.xml.sax;
+
+import java.io.IOException;
+
+
+/**
+ * Basic interface for resolving entities.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>If a SAX application needs to implement customized handling
+ * for external entities, it must implement this interface and
+ * register an instance with the SAX driver using the
+ * {@link org.xml.sax.XMLReader#setEntityResolver setEntityResolver}
+ * method.</p>
+ *
+ * <p>The XML reader will then allow the application to intercept any
+ * external entities (including the external DTD subset and external
+ * parameter entities, if any) before including them.</p>
+ *
+ * <p>Many SAX applications will not need to implement this interface,
+ * but it will be especially useful for applications that build
+ * XML documents from databases or other specialised input sources,
+ * or for applications that use URI types other than URLs.</p>
+ *
+ * <p>The following resolver would provide the application
+ * with a special character stream for the entity with the system
+ * identifier "http://www.myhost.com/today":</p>
+ *
+ * <pre>
+ * import org.xml.sax.EntityResolver;
+ * import org.xml.sax.InputSource;
+ *
+ * public class MyResolver implements EntityResolver {
+ *   public InputSource resolveEntity (String publicId, String systemId)
+ *   {
+ *     if (systemId.equals("http://www.myhost.com/today")) {
+ *              // return a special input source
+ *       MyReader reader = new MyReader();
+ *       return new InputSource(reader);
+ *     } else {
+ *              // use the default behaviour
+ *       return null;
+ *     }
+ *   }
+ * }
+ * </pre>
+ *
+ * <p>The application can also use this interface to redirect system
+ * identifiers to local URIs or to look up replacements in a catalog
+ * (possibly by using the public identifier).</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.XMLReader#setEntityResolver
+ * @see org.xml.sax.InputSource
+ */
+public interface EntityResolver {
+    
+    
+    /**
+     * Allow the application to resolve external entities.
+     *
+     * <p>The parser will call this method before opening any external
+     * entity except the top-level document entity.  Such entities include
+     * the external DTD subset and external parameter entities referenced
+     * within the DTD (in either case, only if the parser reads external
+     * parameter entities), and external general entities referenced
+     * within the document element (if the parser reads external general
+     * entities).  The application may request that the parser locate
+     * the entity itself, that it use an alternative URI, or that it
+     * use data provided by the application (as a character or byte
+     * input stream).</p>
+     *
+     * <p>Application writers can use this method to redirect external
+     * system identifiers to secure and/or local URIs, to look up
+     * public identifiers in a catalogue, or to read an entity from a
+     * database or other input source (including, for example, a dialog
+     * box).  Neither XML nor SAX specifies a preferred policy for using
+     * public or system IDs to resolve resources.  However, SAX specifies
+     * how to interpret any InputSource returned by this method, and that
+     * if none is returned, then the system ID will be dereferenced as
+     * a URL.  </p>
+     *
+     * <p>If the system identifier is a URL, the SAX parser must
+     * resolve it fully before reporting it to the application.</p>
+     *
+     * @param publicId The public identifier of the external entity
+     *        being referenced, or null if none was supplied.
+     * @param systemId The system identifier of the external entity
+     *        being referenced.
+     * @return An InputSource object describing the new input source,
+     *         or null to request that the parser open a regular
+     *         URI connection to the system identifier.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @exception java.io.IOException A Java-specific IO exception,
+     *            possibly the result of creating a new InputStream
+     *            or Reader for the InputSource.
+     * @see org.xml.sax.InputSource
+     */
+    public abstract InputSource resolveEntity (String publicId,
+                                              String systemId)
+       throws SAXException, IOException;
+    
+}
+
+// end of EntityResolver.java
diff --git a/src/org/xml/sax/ErrorHandler.java b/src/org/xml/sax/ErrorHandler.java
new file mode 100644 (file)
index 0000000..37d2501
--- /dev/null
@@ -0,0 +1,139 @@
+// SAX error handler.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: ErrorHandler.java,v 1.10 2004/03/08 13:01:00 dmegginson Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Basic interface for SAX error handlers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>If a SAX application needs to implement customized error
+ * handling, it must implement this interface and then register an
+ * instance with the XML reader using the
+ * {@link org.xml.sax.XMLReader#setErrorHandler setErrorHandler}
+ * method.  The parser will then report all errors and warnings
+ * through this interface.</p>
+ *
+ * <p><strong>WARNING:</strong> If an application does <em>not</em>
+ * register an ErrorHandler, XML parsing errors will go unreported,
+ * except that <em>SAXParseException</em>s will be thrown for fatal errors.
+ * In order to detect validity errors, an ErrorHandler that does something
+ * with {@link #error error()} calls must be registered.</p>
+ *
+ * <p>For XML processing errors, a SAX driver must use this interface 
+ * in preference to throwing an exception: it is up to the application 
+ * to decide whether to throw an exception for different types of 
+ * errors and warnings.  Note, however, that there is no requirement that 
+ * the parser continue to report additional errors after a call to 
+ * {@link #fatalError fatalError}.  In other words, a SAX driver class 
+ * may throw an exception after reporting any fatalError.
+ * Also parsers may throw appropriate exceptions for non-XML errors.
+ * For example, {@link XMLReader#parse XMLReader.parse()} would throw
+ * an IOException for errors accessing entities or the document.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1+ (sax2r3pre1)
+ * @see org.xml.sax.XMLReader#setErrorHandler
+ * @see org.xml.sax.SAXParseException 
+ */
+public interface ErrorHandler {
+    
+    
+    /**
+     * Receive notification of a warning.
+     *
+     * <p>SAX parsers will use this method to report conditions that
+     * are not errors or fatal errors as defined by the XML
+     * recommendation.  The default behaviour is to take no
+     * action.</p>
+     *
+     * <p>The SAX parser must continue to provide normal parsing events
+     * after invoking this method: it should still be possible for the
+     * application to process the document through to the end.</p>
+     *
+     * <p>Filters may use this method to report other, non-XML warnings
+     * as well.</p>
+     *
+     * @param exception The warning information encapsulated in a
+     *                  SAX parse exception.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.SAXParseException 
+     */
+    public abstract void warning (SAXParseException exception)
+       throws SAXException;
+    
+    
+    /**
+     * Receive notification of a recoverable error.
+     *
+     * <p>This corresponds to the definition of "error" in section 1.2
+     * of the W3C XML 1.0 Recommendation.  For example, a validating
+     * parser would use this callback to report the violation of a
+     * validity constraint.  The default behaviour is to take no
+     * action.</p>
+     *
+     * <p>The SAX parser must continue to provide normal parsing
+     * events after invoking this method: it should still be possible
+     * for the application to process the document through to the end.
+     * If the application cannot do so, then the parser should report
+     * a fatal error even if the XML recommendation does not require
+     * it to do so.</p>
+     *
+     * <p>Filters may use this method to report other, non-XML errors
+     * as well.</p>
+     *
+     * @param exception The error information encapsulated in a
+     *                  SAX parse exception.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.SAXParseException 
+     */
+    public abstract void error (SAXParseException exception)
+       throws SAXException;
+    
+    
+    /**
+     * Receive notification of a non-recoverable error.
+     *
+     * <p><strong>There is an apparent contradiction between the
+     * documentation for this method and the documentation for {@link
+     * org.xml.sax.ContentHandler#endDocument}.  Until this ambiguity
+     * is resolved in a future major release, clients should make no
+     * assumptions about whether endDocument() will or will not be
+     * invoked when the parser has reported a fatalError() or thrown
+     * an exception.</strong></p>
+     *
+     * <p>This corresponds to the definition of "fatal error" in
+     * section 1.2 of the W3C XML 1.0 Recommendation.  For example, a
+     * parser would use this callback to report the violation of a
+     * well-formedness constraint.</p>
+     *
+     * <p>The application must assume that the document is unusable
+     * after the parser has invoked this method, and should continue
+     * (if at all) only for the sake of collecting additional error
+     * messages: in fact, SAX parsers are free to stop reporting any
+     * other events once this method has been invoked.</p>
+     *
+     * @param exception The error information encapsulated in a
+     *                  SAX parse exception.  
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.SAXParseException
+     */
+    public abstract void fatalError (SAXParseException exception)
+       throws SAXException;
+    
+}
+
+// end of ErrorHandler.java
diff --git a/src/org/xml/sax/HandlerBase.java b/src/org/xml/sax/HandlerBase.java
new file mode 100644 (file)
index 0000000..7c5c411
--- /dev/null
@@ -0,0 +1,383 @@
+// SAX default handler base class.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: HandlerBase.java,v 1.7 2004/04/26 17:34:34 dmegginson Exp $
+
+package org.xml.sax;
+
+/**
+ * Default base class for handlers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class implements the default behaviour for four SAX1
+ * interfaces: EntityResolver, DTDHandler, DocumentHandler,
+ * and ErrorHandler.  It is now obsolete, but is included in SAX2 to
+ * support legacy SAX1 applications.  SAX2 applications should use
+ * the {@link org.xml.sax.helpers.DefaultHandler DefaultHandler}
+ * class instead.</p>
+ *
+ * <p>Application writers can extend this class when they need to
+ * implement only part of an interface; parser writers can
+ * instantiate this class to provide default handlers when the
+ * application has not supplied its own.</p>
+ *
+ * <p>Note that the use of this class is optional.</p>
+ *
+ * @deprecated This class works with the deprecated
+ *             {@link org.xml.sax.DocumentHandler DocumentHandler}
+ *             interface.  It has been replaced by the SAX2
+ *             {@link org.xml.sax.helpers.DefaultHandler DefaultHandler}
+ *             class.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.EntityResolver
+ * @see org.xml.sax.DTDHandler
+ * @see org.xml.sax.DocumentHandler
+ * @see org.xml.sax.ErrorHandler
+ */
+public class HandlerBase
+    implements EntityResolver, DTDHandler, DocumentHandler, ErrorHandler
+{
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Default implementation of the EntityResolver interface.
+    ////////////////////////////////////////////////////////////////////
+    
+    /**
+     * Resolve an external entity.
+     *
+     * <p>Always return null, so that the parser will use the system
+     * identifier provided in the XML document.  This method implements
+     * the SAX default behaviour: application writers can override it
+     * in a subclass to do special translations such as catalog lookups
+     * or URI redirection.</p>
+     *
+     * @param publicId The public identifer, or null if none is
+     *                 available.
+     * @param systemId The system identifier provided in the XML 
+     *                 document.
+     * @return The new input source, or null to require the
+     *         default behaviour.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.EntityResolver#resolveEntity
+     */
+    @Override
+               public InputSource resolveEntity (String publicId, String systemId)
+       throws SAXException
+    {
+       return null;
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Default implementation of DTDHandler interface.
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Receive notification of a notation declaration.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass if they wish to keep track of the notations
+     * declared in a document.</p>
+     *
+     * @param name The notation name.
+     * @param publicId The notation public identifier, or null if not
+     *                 available.
+     * @param systemId The notation system identifier.
+     * @see org.xml.sax.DTDHandler#notationDecl
+     */
+    @Override
+               public void notationDecl (String name, String publicId, String systemId)
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of an unparsed entity declaration.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to keep track of the unparsed entities
+     * declared in a document.</p>
+     *
+     * @param name The entity name.
+     * @param publicId The entity public identifier, or null if not
+     *                 available.
+     * @param systemId The entity system identifier.
+     * @param notationName The name of the associated notation.
+     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+     */
+    @Override
+               public void unparsedEntityDecl (String name, String publicId,
+                                   String systemId, String notationName)
+    {
+       // no op
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Default implementation of DocumentHandler interface.
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Receive a Locator object for document events.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass if they wish to store the locator for use
+     * with other document events.</p>
+     *
+     * @param locator A locator for all SAX document events.
+     * @see org.xml.sax.DocumentHandler#setDocumentLocator
+     * @see org.xml.sax.Locator
+     */
+    @Override
+               public void setDocumentLocator (Locator locator)
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of the beginning of the document.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions at the beginning
+     * of a document (such as allocating the root node of a tree or
+     * creating an output file).</p>
+     *
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.DocumentHandler#startDocument
+     */
+    @Override
+               public void startDocument ()
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of the end of the document.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions at the beginning
+     * of a document (such as finalising a tree or closing an output
+     * file).</p>
+     *
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.DocumentHandler#endDocument
+     */
+    @Override
+               public void endDocument ()
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of the start of an element.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions at the start of
+     * each element (such as allocating a new tree node or writing
+     * output to a file).</p>
+     *
+     * @param name The element type name.
+     * @param attributes The specified or defaulted attributes.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.DocumentHandler#startElement
+     */
+    @Override
+               public void startElement (String name, AttributeList attributes)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of the end of an element.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions at the end of
+     * each element (such as finalising a tree node or writing
+     * output to a file).</p>
+     *
+     * @param name the element name
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.DocumentHandler#endElement
+     */
+    @Override
+               public void endElement (String name)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of character data inside an element.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method to take specific actions for each chunk of character data
+     * (such as adding the data to a node or buffer, or printing it to
+     * a file).</p>
+     *
+     * @param ch The characters.
+     * @param start The start position in the character array.
+     * @param length The number of characters to use from the
+     *               character array.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.DocumentHandler#characters
+     */
+    @Override
+               public void characters (char ch[], int start, int length)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of ignorable whitespace in element content.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method to take specific actions for each chunk of ignorable
+     * whitespace (such as adding data to a node or buffer, or printing
+     * it to a file).</p>
+     *
+     * @param ch The whitespace characters.
+     * @param start The start position in the character array.
+     * @param length The number of characters to use from the
+     *               character array.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.DocumentHandler#ignorableWhitespace
+     */
+    @Override
+               public void ignorableWhitespace (char ch[], int start, int length)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of a processing instruction.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions for each
+     * processing instruction, such as setting status variables or
+     * invoking other methods.</p>
+     *
+     * @param target The processing instruction target.
+     * @param data The processing instruction data, or null if
+     *             none is supplied.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.DocumentHandler#processingInstruction
+     */
+    @Override
+               public void processingInstruction (String target, String data)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Default implementation of the ErrorHandler interface.
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Receive notification of a parser warning.
+     *
+     * <p>The default implementation does nothing.  Application writers
+     * may override this method in a subclass to take specific actions
+     * for each warning, such as inserting the message in a log file or
+     * printing it to the console.</p>
+     *
+     * @param e The warning information encoded as an exception.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ErrorHandler#warning
+     * @see org.xml.sax.SAXParseException
+     */
+    @Override
+               public void warning (SAXParseException e)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of a recoverable parser error.
+     *
+     * <p>The default implementation does nothing.  Application writers
+     * may override this method in a subclass to take specific actions
+     * for each error, such as inserting the message in a log file or
+     * printing it to the console.</p>
+     *
+     * @param e The warning information encoded as an exception.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ErrorHandler#warning
+     * @see org.xml.sax.SAXParseException
+     */
+    @Override
+               public void error (SAXParseException e)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Report a fatal XML parsing error.
+     *
+     * <p>The default implementation throws a SAXParseException.
+     * Application writers may override this method in a subclass if
+     * they need to take specific actions for each fatal error (such as
+     * collecting all of the errors into a single report): in any case,
+     * the application must stop all regular processing when this
+     * method is invoked, since the document is no longer reliable, and
+     * the parser may no longer report parsing events.</p>
+     *
+     * @param e The error information encoded as an exception.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ErrorHandler#fatalError
+     * @see org.xml.sax.SAXParseException
+     */
+    @Override
+               public void fatalError (SAXParseException e)
+       throws SAXException
+    {
+       throw e;
+    }
+    
+}
+
+// end of HandlerBase.java
diff --git a/src/org/xml/sax/InputSource.java b/src/org/xml/sax/InputSource.java
new file mode 100644 (file)
index 0000000..663700c
--- /dev/null
@@ -0,0 +1,336 @@
+// SAX input source.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: InputSource.java,v 1.9 2002/01/30 21:13:45 dbrownell Exp $
+
+package org.xml.sax;
+
+import java.io.Reader;
+import java.io.InputStream;
+
+/**
+ * A single input source for an XML entity.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class allows a SAX application to encapsulate information
+ * about an input source in a single object, which may include
+ * a public identifier, a system identifier, a byte stream (possibly
+ * with a specified encoding), and/or a character stream.</p>
+ *
+ * <p>There are two places that the application can deliver an
+ * input source to the parser: as the argument to the Parser.parse
+ * method, or as the return value of the EntityResolver.resolveEntity
+ * method.</p>
+ *
+ * <p>The SAX parser will use the InputSource object to determine how
+ * to read XML input.  If there is a character stream available, the
+ * parser will read that stream directly, disregarding any text
+ * encoding declaration found in that stream.
+ * If there is no character stream, but there is
+ * a byte stream, the parser will use that byte stream, using the
+ * encoding specified in the InputSource or else (if no encoding is
+ * specified) autodetecting the character encoding using an algorithm
+ * such as the one in the XML specification.  If neither a character
+ * stream nor a
+ * byte stream is available, the parser will attempt to open a URI
+ * connection to the resource identified by the system
+ * identifier.</p>
+ *
+ * <p>An InputSource object belongs to the application: the SAX parser
+ * shall never modify it in any way (it may modify a copy if 
+ * necessary).  However, standard processing of both byte and
+ * character streams is to close them on as part of end-of-parse cleanup,
+ * so applications should not attempt to re-use such streams after they
+ * have been handed to a parser.  </p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.XMLReader#parse(org.xml.sax.InputSource)
+ * @see org.xml.sax.EntityResolver#resolveEntity
+ * @see java.io.InputStream
+ * @see java.io.Reader
+ */
+public class InputSource {
+    
+    /**
+     * Zero-argument default constructor.
+     *
+     * @see #setPublicId
+     * @see #setSystemId
+     * @see #setByteStream
+     * @see #setCharacterStream
+     * @see #setEncoding
+     */
+    public InputSource ()
+    {
+    }
+    
+    
+    /**
+     * Create a new input source with a system identifier.
+     *
+     * <p>Applications may use setPublicId to include a 
+     * public identifier as well, or setEncoding to specify
+     * the character encoding, if known.</p>
+     *
+     * <p>If the system identifier is a URL, it must be fully
+     * resolved (it may not be a relative URL).</p>
+     *
+     * @param systemId The system identifier (URI).
+     * @see #setPublicId
+     * @see #setSystemId
+     * @see #setByteStream
+     * @see #setEncoding
+     * @see #setCharacterStream
+     */
+    public InputSource (String systemId)
+    {
+       setSystemId(systemId);
+    }
+    
+    
+    /**
+     * Create a new input source with a byte stream.
+     *
+     * <p>Application writers should use setSystemId() to provide a base 
+     * for resolving relative URIs, may use setPublicId to include a 
+     * public identifier, and may use setEncoding to specify the object's
+     * character encoding.</p>
+     *
+     * @param byteStream The raw byte stream containing the document.
+     * @see #setPublicId
+     * @see #setSystemId
+     * @see #setEncoding
+     * @see #setByteStream
+     * @see #setCharacterStream
+     */
+    public InputSource (InputStream byteStream)
+    {
+       setByteStream(byteStream);
+    }
+    
+    
+    /**
+     * Create a new input source with a character stream.
+     *
+     * <p>Application writers should use setSystemId() to provide a base 
+     * for resolving relative URIs, and may use setPublicId to include a 
+     * public identifier.</p>
+     *
+     * <p>The character stream shall not include a byte order mark.</p>
+     *
+     * @see #setPublicId
+     * @see #setSystemId
+     * @see #setByteStream
+     * @see #setCharacterStream
+     */
+    public InputSource (Reader characterStream)
+    {
+       setCharacterStream(characterStream);
+    }
+    
+    
+    /**
+     * Set the public identifier for this input source.
+     * 
+     * <p>The public identifier is always optional: if the application
+     * writer includes one, it will be provided as part of the
+     * location information.</p>
+     *
+     * @param publicId The public identifier as a string.
+     * @see #getPublicId
+     * @see org.xml.sax.Locator#getPublicId
+     * @see org.xml.sax.SAXParseException#getPublicId
+     */
+    public void setPublicId (String publicId)
+    {
+       this.publicId = publicId;
+    }
+    
+    
+    /**
+     * Get the public identifier for this input source.
+     *
+     * @return The public identifier, or null if none was supplied.
+     * @see #setPublicId
+     */
+    public String getPublicId ()
+    {
+       return publicId;
+    }
+    
+    
+    /**
+     * Set the system identifier for this input source.
+     *
+     * <p>The system identifier is optional if there is a byte stream
+     * or a character stream, but it is still useful to provide one,
+     * since the application can use it to resolve relative URIs
+     * and can include it in error messages and warnings (the parser
+     * will attempt to open a connection to the URI only if
+     * there is no byte stream or character stream specified).</p>
+     *
+     * <p>If the application knows the character encoding of the
+     * object pointed to by the system identifier, it can register
+     * the encoding using the setEncoding method.</p>
+     *
+     * <p>If the system identifier is a URL, it must be fully
+     * resolved (it may not be a relative URL).</p>
+     *
+     * @param systemId The system identifier as a string.
+     * @see #setEncoding
+     * @see #getSystemId
+     * @see org.xml.sax.Locator#getSystemId
+     * @see org.xml.sax.SAXParseException#getSystemId
+     */
+    public void setSystemId (String systemId)
+    {
+       this.systemId = systemId;
+    }
+    
+    
+    /**
+     * Get the system identifier for this input source.
+     *
+     * <p>The getEncoding method will return the character encoding
+     * of the object pointed to, or null if unknown.</p>
+     *
+     * <p>If the system ID is a URL, it will be fully resolved.</p>
+     *
+     * @return The system identifier, or null if none was supplied.
+     * @see #setSystemId
+     * @see #getEncoding
+     */
+    public String getSystemId ()
+    {
+       return systemId;
+    }
+    
+    
+    /**
+     * Set the byte stream for this input source.
+     *
+     * <p>The SAX parser will ignore this if there is also a character
+     * stream specified, but it will use a byte stream in preference
+     * to opening a URI connection itself.</p>
+     *
+     * <p>If the application knows the character encoding of the
+     * byte stream, it should set it with the setEncoding method.</p>
+     *
+     * @param byteStream A byte stream containing an XML document or
+     *        other entity.
+     * @see #setEncoding
+     * @see #getByteStream
+     * @see #getEncoding
+     * @see java.io.InputStream
+     */
+    public void setByteStream (InputStream byteStream)
+    {
+       this.byteStream = byteStream;
+    }
+    
+    
+    /**
+     * Get the byte stream for this input source.
+     *
+     * <p>The getEncoding method will return the character
+     * encoding for this byte stream, or null if unknown.</p>
+     *
+     * @return The byte stream, or null if none was supplied.
+     * @see #getEncoding
+     * @see #setByteStream
+     */
+    public InputStream getByteStream ()
+    {
+       return byteStream;
+    }
+    
+    
+    /** 
+     * Set the character encoding, if known.
+     *
+     * <p>The encoding must be a string acceptable for an
+     * XML encoding declaration (see section 4.3.3 of the XML 1.0
+     * recommendation).</p>
+     *
+     * <p>This method has no effect when the application provides a
+     * character stream.</p>
+     *
+     * @param encoding A string describing the character encoding.
+     * @see #setSystemId
+     * @see #setByteStream
+     * @see #getEncoding
+     */
+    public void setEncoding (String encoding)
+    {
+       this.encoding = encoding;
+    }
+    
+    
+    /**
+     * Get the character encoding for a byte stream or URI.
+     * This value will be ignored when the application provides a
+     * character stream.
+     *
+     * @return The encoding, or null if none was supplied.
+     * @see #setByteStream
+     * @see #getSystemId
+     * @see #getByteStream
+     */
+    public String getEncoding ()
+    {
+       return encoding;
+    }
+    
+    
+    /**
+     * Set the character stream for this input source.
+     *
+     * <p>If there is a character stream specified, the SAX parser
+     * will ignore any byte stream and will not attempt to open
+     * a URI connection to the system identifier.</p>
+     *
+     * @param characterStream The character stream containing the
+     *        XML document or other entity.
+     * @see #getCharacterStream
+     * @see java.io.Reader
+     */
+    public void setCharacterStream (Reader characterStream)
+    {
+       this.characterStream = characterStream;
+    }
+    
+    
+    /**
+     * Get the character stream for this input source.
+     *
+     * @return The character stream, or null if none was supplied.
+     * @see #setCharacterStream
+     */
+    public Reader getCharacterStream ()
+    {
+       return characterStream;
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal state.
+    ////////////////////////////////////////////////////////////////////
+    
+    private String publicId;
+    private String systemId;
+    private InputStream byteStream;
+    private String encoding;
+    private Reader characterStream;
+    
+}
+
+// end of InputSource.java
diff --git a/src/org/xml/sax/Locator.java b/src/org/xml/sax/Locator.java
new file mode 100644 (file)
index 0000000..f8f3484
--- /dev/null
@@ -0,0 +1,136 @@
+// SAX locator interface for document events.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: Locator.java,v 1.8 2002/01/30 21:13:47 dbrownell Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Interface for associating a SAX event with a document location.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>If a SAX parser provides location information to the SAX
+ * application, it does so by implementing this interface and then
+ * passing an instance to the application using the content
+ * handler's {@link org.xml.sax.ContentHandler#setDocumentLocator
+ * setDocumentLocator} method.  The application can use the
+ * object to obtain the location of any other SAX event
+ * in the XML source document.</p>
+ *
+ * <p>Note that the results returned by the object will be valid only
+ * during the scope of each callback method: the application
+ * will receive unpredictable results if it attempts to use the
+ * locator at any other time, or after parsing completes.</p>
+ *
+ * <p>SAX parsers are not required to supply a locator, but they are
+ * very strongly encouraged to do so.  If the parser supplies a
+ * locator, it must do so before reporting any other document events.
+ * If no locator has been set by the time the application receives
+ * the {@link org.xml.sax.ContentHandler#startDocument startDocument}
+ * event, the application should assume that a locator is not 
+ * available.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.ContentHandler#setDocumentLocator 
+ */
+public interface Locator {
+    
+    
+    /**
+     * Return the public identifier for the current document event.
+     *
+     * <p>The return value is the public identifier of the document
+     * entity or of the external parsed entity in which the markup
+     * triggering the event appears.</p>
+     *
+     * @return A string containing the public identifier, or
+     *         null if none is available.
+     * @see #getSystemId
+     */
+    public abstract String getPublicId ();
+    
+    
+    /**
+     * Return the system identifier for the current document event.
+     *
+     * <p>The return value is the system identifier of the document
+     * entity or of the external parsed entity in which the markup
+     * triggering the event appears.</p>
+     *
+     * <p>If the system identifier is a URL, the parser must resolve it
+     * fully before passing it to the application.  For example, a file
+     * name must always be provided as a <em>file:...</em> URL, and other
+     * kinds of relative URI are also resolved against their bases.</p>
+     *
+     * @return A string containing the system identifier, or null
+     *         if none is available.
+     * @see #getPublicId
+     */
+    public abstract String getSystemId ();
+    
+    
+    /**
+     * Return the line number where the current document event ends.
+     * Lines are delimited by line ends, which are defined in
+     * the XML specification.
+     *
+     * <p><strong>Warning:</strong> The return value from the method
+     * is intended only as an approximation for the sake of diagnostics;
+     * it is not intended to provide sufficient information
+     * to edit the character content of the original XML document.
+     * In some cases, these "line" numbers match what would be displayed
+     * as columns, and in others they may not match the source text
+     * due to internal entity expansion.  </p>
+     *
+     * <p>The return value is an approximation of the line number
+     * in the document entity or external parsed entity where the
+     * markup triggering the event appears.</p>
+     *
+     * <p>If possible, the SAX driver should provide the line position 
+     * of the first character after the text associated with the document 
+     * event.  The first line is line 1.</p>
+     *
+     * @return The line number, or -1 if none is available.
+     * @see #getColumnNumber
+     */
+    public abstract int getLineNumber ();
+    
+    
+    /**
+     * Return the column number where the current document event ends.
+     * This is one-based number of Java <code>char</code> values since
+     * the last line end.
+     *
+     * <p><strong>Warning:</strong> The return value from the method
+     * is intended only as an approximation for the sake of diagnostics;
+     * it is not intended to provide sufficient information
+     * to edit the character content of the original XML document.
+     * For example, when lines contain combining character sequences, wide
+     * characters, surrogate pairs, or bi-directional text, the value may
+     * not correspond to the column in a text editor's display. </p>
+     *
+     * <p>The return value is an approximation of the column number
+     * in the document entity or external parsed entity where the
+     * markup triggering the event appears.</p>
+     *
+     * <p>If possible, the SAX driver should provide the line position 
+     * of the first character after the text associated with the document 
+     * event.  The first column in each line is column 1.</p>
+     *
+     * @return The column number, or -1 if none is available.
+     * @see #getLineNumber
+     */
+    public abstract int getColumnNumber ();
+    
+}
+
+// end of Locator.java
diff --git a/src/org/xml/sax/Parser.java b/src/org/xml/sax/Parser.java
new file mode 100644 (file)
index 0000000..734819d
--- /dev/null
@@ -0,0 +1,209 @@
+// SAX parser interface.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: Parser.java,v 1.6 2002/01/30 21:13:47 dbrownell Exp $
+
+package org.xml.sax;
+
+import java.io.IOException;
+import java.util.Locale;
+
+
+/**
+ * Basic interface for SAX (Simple API for XML) parsers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This was the main event supplier interface for SAX1; it has
+ * been replaced in SAX2 by {@link org.xml.sax.XMLReader XMLReader},
+ * which includes Namespace support and sophisticated configurability
+ * and extensibility.</p>
+ *
+ * <p>All SAX1 parsers must implement this basic interface: it allows
+ * applications to register handlers for different types of events
+ * and to initiate a parse from a URI, or a character stream.</p>
+ *
+ * <p>All SAX1 parsers must also implement a zero-argument constructor
+ * (though other constructors are also allowed).</p>
+ *
+ * <p>SAX1 parsers are reusable but not re-entrant: the application
+ * may reuse a parser object (possibly with a different input source)
+ * once the first parse has completed successfully, but it may not
+ * invoke the parse() methods recursively within a parse.</p>
+ *
+ * @deprecated This interface has been replaced by the SAX2
+ *             {@link org.xml.sax.XMLReader XMLReader}
+ *             interface, which includes Namespace support.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.EntityResolver
+ * @see org.xml.sax.DTDHandler
+ * @see org.xml.sax.DocumentHandler
+ * @see org.xml.sax.ErrorHandler
+ * @see org.xml.sax.HandlerBase
+ * @see org.xml.sax.InputSource
+ */
+public interface Parser 
+{
+    
+    /**
+     * Allow an application to request a locale for errors and warnings.
+     *
+     * <p>SAX parsers are not required to provide localisation for errors
+     * and warnings; if they cannot support the requested locale,
+     * however, they must throw a SAX exception.  Applications may
+     * not request a locale change in the middle of a parse.</p>
+     *
+     * @param locale A Java Locale object.
+     * @exception org.xml.sax.SAXException Throws an exception
+     *            (using the previous or default locale) if the 
+     *            requested locale is not supported.
+     * @see org.xml.sax.SAXException
+     * @see org.xml.sax.SAXParseException
+     */
+    public abstract void setLocale (Locale locale)
+       throws SAXException;
+    
+    
+    /**
+     * Allow an application to register a custom entity resolver.
+     *
+     * <p>If the application does not register an entity resolver, the
+     * SAX parser will resolve system identifiers and open connections
+     * to entities itself (this is the default behaviour implemented in
+     * HandlerBase).</p>
+     *
+     * <p>Applications may register a new or different entity resolver
+     * in the middle of a parse, and the SAX parser must begin using
+     * the new resolver immediately.</p>
+     *
+     * @param resolver The object for resolving entities.
+     * @see EntityResolver
+     * @see HandlerBase
+     */
+    public abstract void setEntityResolver (EntityResolver resolver);
+    
+    
+    /**
+     * Allow an application to register a DTD event handler.
+     *
+     * <p>If the application does not register a DTD handler, all DTD
+     * events reported by the SAX parser will be silently
+     * ignored (this is the default behaviour implemented by
+     * HandlerBase).</p>
+     *
+     * <p>Applications may register a new or different
+     * handler in the middle of a parse, and the SAX parser must
+     * begin using the new handler immediately.</p>
+     *
+     * @param handler The DTD handler.
+     * @see DTDHandler
+     * @see HandlerBase
+     */
+    public abstract void setDTDHandler (DTDHandler handler);
+    
+    
+    /**
+     * Allow an application to register a document event handler.
+     *
+     * <p>If the application does not register a document handler, all
+     * document events reported by the SAX parser will be silently
+     * ignored (this is the default behaviour implemented by
+     * HandlerBase).</p>
+     *
+     * <p>Applications may register a new or different handler in the
+     * middle of a parse, and the SAX parser must begin using the new
+     * handler immediately.</p>
+     *
+     * @param handler The document handler.
+     * @see DocumentHandler
+     * @see HandlerBase
+     */
+    public abstract void setDocumentHandler (DocumentHandler handler);
+    
+    
+    /**
+     * Allow an application to register an error event handler.
+     *
+     * <p>If the application does not register an error event handler,
+     * all error events reported by the SAX parser will be silently
+     * ignored, except for fatalError, which will throw a SAXException
+     * (this is the default behaviour implemented by HandlerBase).</p>
+     *
+     * <p>Applications may register a new or different handler in the
+     * middle of a parse, and the SAX parser must begin using the new
+     * handler immediately.</p>
+     *
+     * @param handler The error handler.
+     * @see ErrorHandler
+     * @see SAXException
+     * @see HandlerBase
+     */
+    public abstract void setErrorHandler (ErrorHandler handler);
+    
+    
+    /**
+     * Parse an XML document.
+     *
+     * <p>The application can use this method to instruct the SAX parser
+     * to begin parsing an XML document from any valid input
+     * source (a character stream, a byte stream, or a URI).</p>
+     *
+     * <p>Applications may not invoke this method while a parse is in
+     * progress (they should create a new Parser instead for each
+     * additional XML document).  Once a parse is complete, an
+     * application may reuse the same Parser object, possibly with a
+     * different input source.</p>
+     *
+     * @param source The input source for the top-level of the
+     *        XML document.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @exception java.io.IOException An IO exception from the parser,
+     *            possibly from a byte stream or character stream
+     *            supplied by the application.
+     * @see org.xml.sax.InputSource
+     * @see #parse(java.lang.String)
+     * @see #setEntityResolver
+     * @see #setDTDHandler
+     * @see #setDocumentHandler
+     * @see #setErrorHandler
+     */
+    public abstract void parse (InputSource source)
+       throws SAXException, IOException;
+    
+    
+    /**
+     * Parse an XML document from a system identifier (URI).
+     *
+     * <p>This method is a shortcut for the common case of reading a
+     * document from a system identifier.  It is the exact
+     * equivalent of the following:</p>
+     *
+     * <pre>
+     * parse(new InputSource(systemId));
+     * </pre>
+     *
+     * <p>If the system identifier is a URL, it must be fully resolved
+     * by the application before it is passed to the parser.</p>
+     *
+     * @param systemId The system identifier (URI).
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @exception java.io.IOException An IO exception from the parser,
+     *            possibly from a byte stream or character stream
+     *            supplied by the application.
+     * @see #parse(org.xml.sax.InputSource)
+     */
+    public abstract void parse (String systemId)
+       throws SAXException, IOException;
+    
+}
+
+// end of Parser.java
diff --git a/src/org/xml/sax/SAXException.java b/src/org/xml/sax/SAXException.java
new file mode 100644 (file)
index 0000000..70a60ce
--- /dev/null
@@ -0,0 +1,155 @@
+// SAX exception class.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: SAXException.java,v 1.7 2002/01/30 21:13:48 dbrownell Exp $
+
+package org.xml.sax;
+
+/**
+ * Encapsulate a general SAX error or warning.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class can contain basic error or warning information from
+ * either the XML parser or the application: a parser writer or
+ * application writer can subclass it to provide additional
+ * functionality.  SAX handlers may throw this exception or
+ * any exception subclassed from it.</p>
+ *
+ * <p>If the application needs to pass through other types of
+ * exceptions, it must wrap those exceptions in a SAXException
+ * or an exception derived from a SAXException.</p>
+ *
+ * <p>If the parser or application needs to include information about a
+ * specific location in an XML document, it should use the
+ * {@link org.xml.sax.SAXParseException SAXParseException} subclass.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.SAXParseException
+ */
+public class SAXException extends Exception {
+
+
+    /**
+     * Create a new SAXException.
+     */
+    public SAXException ()
+    {
+       super();
+       this.exception = null;
+    }
+    
+    
+    /**
+     * Create a new SAXException.
+     *
+     * @param message The error or warning message.
+     */
+    public SAXException (String message) {
+       super(message);
+       this.exception = null;
+    }
+    
+    
+    /**
+     * Create a new SAXException wrapping an existing exception.
+     *
+     * <p>The existing exception will be embedded in the new
+     * one, and its message will become the default message for
+     * the SAXException.</p>
+     *
+     * @param e The exception to be wrapped in a SAXException.
+     */
+    public SAXException (Exception e)
+    {
+       super();
+       this.exception = e;
+    }
+    
+    
+    /**
+     * Create a new SAXException from an existing exception.
+     *
+     * <p>The existing exception will be embedded in the new
+     * one, but the new exception will have its own message.</p>
+     *
+     * @param message The detail message.
+     * @param e The exception to be wrapped in a SAXException.
+     */
+    public SAXException (String message, Exception e)
+    {
+       super(message);
+       this.exception = e;
+    }
+    
+    
+    /**
+     * Return a detail message for this exception.
+     *
+     * <p>If there is an embedded exception, and if the SAXException
+     * has no detail message of its own, this method will return
+     * the detail message from the embedded exception.</p>
+     *
+     * @return The error or warning message.
+     */
+    @Override
+               public String getMessage ()
+    {
+       String message = super.getMessage();
+       
+       if (message == null && exception != null) {
+           return exception.getMessage();
+       } else {
+           return message;
+       }
+    }
+    
+    
+    /**
+     * Return the embedded exception, if any.
+     *
+     * @return The embedded exception, or null if there is none.
+     */
+    public Exception getException ()
+    {
+       return exception;
+    }
+
+
+    /**
+     * Override toString to pick up any embedded exception.
+     *
+     * @return A string representation of this exception.
+     */
+    @Override
+               public String toString ()
+    {
+       if (exception != null) {
+           return exception.toString();
+       } else {
+           return super.toString();
+       }
+    }
+    
+    
+    \f
+    //////////////////////////////////////////////////////////////////////
+    // Internal state.
+    //////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * @serial The embedded exception if tunnelling, or null.
+     */    
+    private Exception exception;
+    
+}
+
+// end of SAXException.java
diff --git a/src/org/xml/sax/SAXNotRecognizedException.java b/src/org/xml/sax/SAXNotRecognizedException.java
new file mode 100644 (file)
index 0000000..0bb1ded
--- /dev/null
@@ -0,0 +1,53 @@
+// SAXNotRecognizedException.java - unrecognized feature or value.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the Public Domain.
+// $Id: SAXNotRecognizedException.java,v 1.7 2002/01/30 21:13:48 dbrownell Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Exception class for an unrecognized identifier.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>An XMLReader will throw this exception when it finds an
+ * unrecognized feature or property identifier; SAX applications and
+ * extensions may use this class for other, similar purposes.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.SAXNotSupportedException
+ */
+public class SAXNotRecognizedException extends SAXException
+{
+
+    /**
+     * Default constructor.
+     */
+    public SAXNotRecognizedException ()
+    {
+       super();
+    }
+
+
+    /**
+     * Construct a new exception with the given message.
+     *
+     * @param message The text message of the exception.
+     */
+    public SAXNotRecognizedException (String message)
+    {
+       super(message);
+    }
+
+}
+
+// end of SAXNotRecognizedException.java
diff --git a/src/org/xml/sax/SAXNotSupportedException.java b/src/org/xml/sax/SAXNotSupportedException.java
new file mode 100644 (file)
index 0000000..9a645a9
--- /dev/null
@@ -0,0 +1,53 @@
+// SAXNotSupportedException.java - unsupported feature or value.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the Public Domain.
+// $Id: SAXNotSupportedException.java,v 1.7 2002/01/30 21:13:48 dbrownell Exp $
+
+package org.xml.sax;
+
+/**
+ * Exception class for an unsupported operation.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>An XMLReader will throw this exception when it recognizes a
+ * feature or property identifier, but cannot perform the requested
+ * operation (setting a state or value).  Other SAX2 applications and
+ * extensions may use this class for similar purposes.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.SAXNotRecognizedException 
+ */
+public class SAXNotSupportedException extends SAXException
+{
+
+    /**
+     * Construct a new exception with no message.
+     */
+    public SAXNotSupportedException ()
+    {
+       super();
+    }
+
+
+    /**
+     * Construct a new exception with the given message.
+     *
+     * @param message The text message of the exception.
+     */
+    public SAXNotSupportedException (String message)
+    {
+       super(message);
+    }
+
+}
+
+// end of SAXNotSupportedException.java
diff --git a/src/org/xml/sax/SAXParseException.java b/src/org/xml/sax/SAXParseException.java
new file mode 100644 (file)
index 0000000..1df5e14
--- /dev/null
@@ -0,0 +1,269 @@
+// SAX exception class.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: SAXParseException.java,v 1.11 2004/04/21 13:05:02 dmegginson Exp $
+
+package org.xml.sax;
+
+/**
+ * Encapsulate an XML parse error or warning.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This exception may include information for locating the error
+ * in the original XML document, as if it came from a {@link Locator}
+ * object.  Note that although the application
+ * will receive a SAXParseException as the argument to the handlers
+ * in the {@link org.xml.sax.ErrorHandler ErrorHandler} interface, 
+ * the application is not actually required to throw the exception; 
+ * instead, it can simply read the information in it and take a 
+ * different action.</p>
+ *
+ * <p>Since this exception is a subclass of {@link org.xml.sax.SAXException 
+ * SAXException}, it inherits the ability to wrap another exception.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.SAXException
+ * @see org.xml.sax.Locator
+ * @see org.xml.sax.ErrorHandler
+ */
+public class SAXParseException extends SAXException {
+    
+    \f
+    //////////////////////////////////////////////////////////////////////
+    // Constructors.
+    //////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Create a new SAXParseException from a message and a Locator.
+     *
+     * <p>This constructor is especially useful when an application is
+     * creating its own exception from within a {@link org.xml.sax.ContentHandler
+     * ContentHandler} callback.</p>
+     *
+     * @param message The error or warning message.
+     * @param locator The locator object for the error or warning (may be
+     *        null).
+     * @see org.xml.sax.Locator
+     */
+    public SAXParseException (String message, Locator locator) {
+       super(message);
+       if (locator != null) {
+           init(locator.getPublicId(), locator.getSystemId(),
+                locator.getLineNumber(), locator.getColumnNumber());
+       } else {
+           init(null, null, -1, -1);
+       }
+    }
+    
+    
+    /**
+     * Wrap an existing exception in a SAXParseException.
+     *
+     * <p>This constructor is especially useful when an application is
+     * creating its own exception from within a {@link org.xml.sax.ContentHandler
+     * ContentHandler} callback, and needs to wrap an existing exception that is not a
+     * subclass of {@link org.xml.sax.SAXException SAXException}.</p>
+     *
+     * @param message The error or warning message, or null to
+     *                use the message from the embedded exception.
+     * @param locator The locator object for the error or warning (may be
+     *        null).
+     * @param e Any exception.
+     * @see org.xml.sax.Locator
+     */
+    public SAXParseException (String message, Locator locator,
+                             Exception e) {
+       super(message, e);
+       if (locator != null) {
+           init(locator.getPublicId(), locator.getSystemId(),
+                locator.getLineNumber(), locator.getColumnNumber());
+       } else {
+           init(null, null, -1, -1);
+       }
+    }
+    
+    
+    /**
+     * Create a new SAXParseException.
+     *
+     * <p>This constructor is most useful for parser writers.</p>
+     *
+     * <p>All parameters except the message are as if
+     * they were provided by a {@link Locator}.  For example, if the
+     * system identifier is a URL (including relative filename), the
+     * caller must resolve it fully before creating the exception.</p>
+     *
+     *
+     * @param message The error or warning message.
+     * @param publicId The public identifier of the entity that generated
+     *                 the error or warning.
+     * @param systemId The system identifier of the entity that generated
+     *                 the error or warning.
+     * @param lineNumber The line number of the end of the text that
+     *                   caused the error or warning.
+     * @param columnNumber The column number of the end of the text that
+     *                     cause the error or warning.
+     */
+    public SAXParseException (String message, String publicId, String systemId,
+                             int lineNumber, int columnNumber)
+    {
+       super(message);
+       init(publicId, systemId, lineNumber, columnNumber);
+    }
+    
+    
+    /**
+     * Create a new SAXParseException with an embedded exception.
+     *
+     * <p>This constructor is most useful for parser writers who
+     * need to wrap an exception that is not a subclass of
+     * {@link org.xml.sax.SAXException SAXException}.</p>
+     *
+     * <p>All parameters except the message and exception are as if
+     * they were provided by a {@link Locator}.  For example, if the
+     * system identifier is a URL (including relative filename), the
+     * caller must resolve it fully before creating the exception.</p>
+     *
+     * @param message The error or warning message, or null to use
+     *                the message from the embedded exception.
+     * @param publicId The public identifier of the entity that generated
+     *                 the error or warning.
+     * @param systemId The system identifier of the entity that generated
+     *                 the error or warning.
+     * @param lineNumber The line number of the end of the text that
+     *                   caused the error or warning.
+     * @param columnNumber The column number of the end of the text that
+     *                     cause the error or warning.
+     * @param e Another exception to embed in this one.
+     */
+    public SAXParseException (String message, String publicId, String systemId,
+                             int lineNumber, int columnNumber, Exception e)
+    {
+       super(message, e);
+       init(publicId, systemId, lineNumber, columnNumber);
+    }
+
+
+    /**
+     * Internal initialization method.
+     *
+     * @param publicId The public identifier of the entity which generated the exception,
+     *        or null.
+     * @param systemId The system identifier of the entity which generated the exception,
+     *        or null.
+     * @param lineNumber The line number of the error, or -1.
+     * @param columnNumber The column number of the error, or -1.
+     */
+    private void init (String publicId, String systemId,
+                      int lineNumber, int columnNumber)
+    {
+       this.publicId = publicId;
+       this.systemId = systemId;
+       this.lineNumber = lineNumber;
+       this.columnNumber = columnNumber;
+    }
+    
+    
+    /**
+     * Get the public identifier of the entity where the exception occurred.
+     *
+     * @return A string containing the public identifier, or null
+     *         if none is available.
+     * @see org.xml.sax.Locator#getPublicId
+     */
+    public String getPublicId ()
+    {
+       return this.publicId;
+    }
+    
+    
+    /**
+     * Get the system identifier of the entity where the exception occurred.
+     *
+     * <p>If the system identifier is a URL, it will have been resolved
+     * fully.</p>
+     *
+     * @return A string containing the system identifier, or null
+     *         if none is available.
+     * @see org.xml.sax.Locator#getSystemId
+     */
+    public String getSystemId ()
+    {
+       return this.systemId;
+    }
+    
+    
+    /**
+     * The line number of the end of the text where the exception occurred.
+     *
+     * <p>The first line is line 1.</p>
+     *
+     * @return An integer representing the line number, or -1
+     *         if none is available.
+     * @see org.xml.sax.Locator#getLineNumber
+     */
+    public int getLineNumber ()
+    {
+       return this.lineNumber;
+    }
+    
+    
+    /**
+     * The column number of the end of the text where the exception occurred.
+     *
+     * <p>The first column in a line is position 1.</p>
+     *
+     * @return An integer representing the column number, or -1
+     *         if none is available.
+     * @see org.xml.sax.Locator#getColumnNumber
+     */
+    public int getColumnNumber ()
+    {
+       return this.columnNumber;
+    }
+    
+    \f
+    //////////////////////////////////////////////////////////////////////
+    // Internal state.
+    //////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * @serial The public identifier, or null.
+     * @see #getPublicId
+     */    
+    private String publicId;
+
+
+    /**
+     * @serial The system identifier, or null.
+     * @see #getSystemId
+     */
+    private String systemId;
+
+
+    /**
+     * @serial The line number, or -1.
+     * @see #getLineNumber
+     */
+    private int lineNumber;
+
+
+    /**
+     * @serial The column number, or -1.
+     * @see #getColumnNumber
+     */
+    private int columnNumber;
+    
+}
+
+// end of SAXParseException.java
diff --git a/src/org/xml/sax/XMLFilter.java b/src/org/xml/sax/XMLFilter.java
new file mode 100644 (file)
index 0000000..5a399fa
--- /dev/null
@@ -0,0 +1,65 @@
+// XMLFilter.java - filter SAX2 events.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the Public Domain.
+// $Id: XMLFilter.java,v 1.6 2002/01/30 21:13:48 dbrownell Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Interface for an XML filter.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>An XML filter is like an XML reader, except that it obtains its
+ * events from another XML reader rather than a primary source like
+ * an XML document or database.  Filters can modify a stream of
+ * events as they pass on to the final application.</p>
+ *
+ * <p>The XMLFilterImpl helper class provides a convenient base
+ * for creating SAX2 filters, by passing on all {@link org.xml.sax.EntityResolver
+ * EntityResolver}, {@link org.xml.sax.DTDHandler DTDHandler},
+ * {@link org.xml.sax.ContentHandler ContentHandler} and {@link org.xml.sax.ErrorHandler
+ * ErrorHandler} events automatically.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.helpers.XMLFilterImpl
+ */
+public interface XMLFilter extends XMLReader
+{
+
+    /**
+     * Set the parent reader.
+     *
+     * <p>This method allows the application to link the filter to
+     * a parent reader (which may be another filter).  The argument
+     * may not be null.</p>
+     *
+     * @param parent The parent reader.
+     */
+    public abstract void setParent (XMLReader parent);
+
+
+    /**
+     * Get the parent reader.
+     *
+     * <p>This method allows the application to query the parent
+     * reader (which may be another filter).  It is generally a
+     * bad idea to perform any operations on the parent reader
+     * directly: they should all pass through this filter.</p>
+     *
+     * @return The parent filter, or null if none has been set.
+     */
+    public abstract XMLReader getParent ();
+
+}
+
+// end of XMLFilter.java
diff --git a/src/org/xml/sax/XMLReader.java b/src/org/xml/sax/XMLReader.java
new file mode 100644 (file)
index 0000000..9c150a0
--- /dev/null
@@ -0,0 +1,404 @@
+// XMLReader.java - read an XML document.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the Public Domain.
+// $Id: XMLReader.java,v 1.9 2004/04/26 17:34:34 dmegginson Exp $
+
+package org.xml.sax;
+
+import java.io.IOException;
+
+
+/**
+ * Interface for reading an XML document using callbacks.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p><strong>Note:</strong> despite its name, this interface does 
+ * <em>not</em> extend the standard Java {@link java.io.Reader Reader} 
+ * interface, because reading XML is a fundamentally different activity 
+ * than reading character data.</p>
+ *
+ * <p>XMLReader is the interface that an XML parser's SAX2 driver must
+ * implement.  This interface allows an application to set and
+ * query features and properties in the parser, to register
+ * event handlers for document processing, and to initiate
+ * a document parse.</p>
+ *
+ * <p>All SAX interfaces are assumed to be synchronous: the
+ * {@link #parse parse} methods must not return until parsing
+ * is complete, and readers must wait for an event-handler callback
+ * to return before reporting the next event.</p>
+ *
+ * <p>This interface replaces the (now deprecated) SAX 1.0 {@link
+ * org.xml.sax.Parser Parser} interface.  The XMLReader interface
+ * contains two important enhancements over the old Parser
+ * interface (as well as some minor ones):</p>
+ *
+ * <ol>
+ * <li>it adds a standard way to query and set features and 
+ *  properties; and</li>
+ * <li>it adds Namespace support, which is required for many
+ *  higher-level XML standards.</li>
+ * </ol>
+ *
+ * <p>There are adapters available to convert a SAX1 Parser to
+ * a SAX2 XMLReader and vice-versa.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1+ (sax2r3pre1)
+ * @see org.xml.sax.XMLFilter
+ * @see org.xml.sax.helpers.ParserAdapter
+ * @see org.xml.sax.helpers.XMLReaderAdapter 
+ */
+public interface XMLReader
+{
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Configuration.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Look up the value of a feature flag.
+     *
+     * <p>The feature name is any fully-qualified URI.  It is
+     * possible for an XMLReader to recognize a feature name but
+     * temporarily be unable to return its value.
+     * Some feature values may be available only in specific
+     * contexts, such as before, during, or after a parse.
+     * Also, some feature values may not be programmatically accessible.
+     * (In the case of an adapter for SAX1 {@link Parser}, there is no
+     * implementation-independent way to expose whether the underlying
+     * parser is performing validation, expanding external entities,
+     * and so forth.) </p>
+     *
+     * <p>All XMLReaders are required to recognize the
+     * http://xml.org/sax/features/namespaces and the
+     * http://xml.org/sax/features/namespace-prefixes feature names.</p>
+     *
+     * <p>Typical usage is something like this:</p>
+     *
+     * <pre>
+     * XMLReader r = new MySAXDriver();
+     *
+     *                         // try to activate validation
+     * try {
+     *   r.setFeature("http://xml.org/sax/features/validation", true);
+     * } catch (SAXException e) {
+     *   System.err.println("Cannot activate validation."); 
+     * }
+     *
+     *                         // register event handlers
+     * r.setContentHandler(new MyContentHandler());
+     * r.setErrorHandler(new MyErrorHandler());
+     *
+     *                         // parse the first document
+     * try {
+     *   r.parse("http://www.foo.com/mydoc.xml");
+     * } catch (IOException e) {
+     *   System.err.println("I/O exception reading XML document");
+     * } catch (SAXException e) {
+     *   System.err.println("XML exception reading document.");
+     * }
+     * </pre>
+     *
+     * <p>Implementors are free (and encouraged) to invent their own features,
+     * using names built on their own URIs.</p>
+     *
+     * @param name The feature name, which is a fully-qualified URI.
+     * @return The current value of the feature (true or false).
+     * @exception org.xml.sax.SAXNotRecognizedException If the feature
+     *            value can't be assigned or retrieved.
+     * @exception org.xml.sax.SAXNotSupportedException When the
+     *            XMLReader recognizes the feature name but 
+     *            cannot determine its value at this time.
+     * @see #setFeature
+     */
+    public boolean getFeature (String name)
+        throws SAXNotRecognizedException, SAXNotSupportedException;
+
+
+    /**
+     * Set the value of a feature flag.
+     *
+     * <p>The feature name is any fully-qualified URI.  It is
+     * possible for an XMLReader to expose a feature value but
+     * to be unable to change the current value.
+     * Some feature values may be immutable or mutable only 
+     * in specific contexts, such as before, during, or after 
+     * a parse.</p>
+     *
+     * <p>All XMLReaders are required to support setting
+     * http://xml.org/sax/features/namespaces to true and
+     * http://xml.org/sax/features/namespace-prefixes to false.</p>
+     *
+     * @param name The feature name, which is a fully-qualified URI.
+     * @param value The requested value of the feature (true or false).
+     * @exception org.xml.sax.SAXNotRecognizedException If the feature
+     *            value can't be assigned or retrieved.
+     * @exception org.xml.sax.SAXNotSupportedException When the
+     *            XMLReader recognizes the feature name but 
+     *            cannot set the requested value.
+     * @see #getFeature
+     */
+    public void setFeature (String name, boolean value)
+       throws SAXNotRecognizedException, SAXNotSupportedException;
+
+
+    /**
+     * Look up the value of a property.
+     *
+     * <p>The property name is any fully-qualified URI.  It is
+     * possible for an XMLReader to recognize a property name but
+     * temporarily be unable to return its value.
+     * Some property values may be available only in specific
+     * contexts, such as before, during, or after a parse.</p>
+     *
+     * <p>XMLReaders are not required to recognize any specific
+     * property names, though an initial core set is documented for
+     * SAX2.</p>
+     *
+     * <p>Implementors are free (and encouraged) to invent their own properties,
+     * using names built on their own URIs.</p>
+     *
+     * @param name The property name, which is a fully-qualified URI.
+     * @return The current value of the property.
+     * @exception org.xml.sax.SAXNotRecognizedException If the property
+     *            value can't be assigned or retrieved.
+     * @exception org.xml.sax.SAXNotSupportedException When the
+     *            XMLReader recognizes the property name but 
+     *            cannot determine its value at this time.
+     * @see #setProperty
+     */
+    public Object getProperty (String name)
+       throws SAXNotRecognizedException, SAXNotSupportedException;
+
+
+    /**
+     * Set the value of a property.
+     *
+     * <p>The property name is any fully-qualified URI.  It is
+     * possible for an XMLReader to recognize a property name but
+     * to be unable to change the current value.
+     * Some property values may be immutable or mutable only 
+     * in specific contexts, such as before, during, or after 
+     * a parse.</p>
+     *
+     * <p>XMLReaders are not required to recognize setting
+     * any specific property names, though a core set is defined by 
+     * SAX2.</p>
+     *
+     * <p>This method is also the standard mechanism for setting
+     * extended handlers.</p>
+     *
+     * @param name The property name, which is a fully-qualified URI.
+     * @param value The requested value for the property.
+     * @exception org.xml.sax.SAXNotRecognizedException If the property
+     *            value can't be assigned or retrieved.
+     * @exception org.xml.sax.SAXNotSupportedException When the
+     *            XMLReader recognizes the property name but 
+     *            cannot set the requested value.
+     */
+    public void setProperty (String name, Object value)
+       throws SAXNotRecognizedException, SAXNotSupportedException;
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Event handlers.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Allow an application to register an entity resolver.
+     *
+     * <p>If the application does not register an entity resolver,
+     * the XMLReader will perform its own default resolution.</p>
+     *
+     * <p>Applications may register a new or different resolver in the
+     * middle of a parse, and the SAX parser must begin using the new
+     * resolver immediately.</p>
+     *
+     * @param resolver The entity resolver.
+     * @see #getEntityResolver
+     */
+    public void setEntityResolver (EntityResolver resolver);
+
+
+    /**
+     * Return the current entity resolver.
+     *
+     * @return The current entity resolver, or null if none
+     *         has been registered.
+     * @see #setEntityResolver
+     */
+    public EntityResolver getEntityResolver ();
+
+
+    /**
+     * Allow an application to register a DTD event handler.
+     *
+     * <p>If the application does not register a DTD handler, all DTD
+     * events reported by the SAX parser will be silently ignored.</p>
+     *
+     * <p>Applications may register a new or different handler in the
+     * middle of a parse, and the SAX parser must begin using the new
+     * handler immediately.</p>
+     *
+     * @param handler The DTD handler.
+     * @see #getDTDHandler
+     */
+    public void setDTDHandler (DTDHandler handler);
+
+
+    /**
+     * Return the current DTD handler.
+     *
+     * @return The current DTD handler, or null if none
+     *         has been registered.
+     * @see #setDTDHandler
+     */
+    public DTDHandler getDTDHandler ();
+
+
+    /**
+     * Allow an application to register a content event handler.
+     *
+     * <p>If the application does not register a content handler, all
+     * content events reported by the SAX parser will be silently
+     * ignored.</p>
+     *
+     * <p>Applications may register a new or different handler in the
+     * middle of a parse, and the SAX parser must begin using the new
+     * handler immediately.</p>
+     *
+     * @param handler The content handler.
+     * @see #getContentHandler
+     */
+    public void setContentHandler (ContentHandler handler);
+
+
+    /**
+     * Return the current content handler.
+     *
+     * @return The current content handler, or null if none
+     *         has been registered.
+     * @see #setContentHandler
+     */
+    public ContentHandler getContentHandler ();
+
+
+    /**
+     * Allow an application to register an error event handler.
+     *
+     * <p>If the application does not register an error handler, all
+     * error events reported by the SAX parser will be silently
+     * ignored; however, normal processing may not continue.  It is
+     * highly recommended that all SAX applications implement an
+     * error handler to avoid unexpected bugs.</p>
+     *
+     * <p>Applications may register a new or different handler in the
+     * middle of a parse, and the SAX parser must begin using the new
+     * handler immediately.</p>
+     *
+     * @param handler The error handler.
+     * @see #getErrorHandler
+     */
+    public void setErrorHandler (ErrorHandler handler);
+
+
+    /**
+     * Return the current error handler.
+     *
+     * @return The current error handler, or null if none
+     *         has been registered.
+     * @see #setErrorHandler
+     */
+    public ErrorHandler getErrorHandler ();
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Parsing.
+    ////////////////////////////////////////////////////////////////////
+
+    /**
+     * Parse an XML document.
+     *
+     * <p>The application can use this method to instruct the XML
+     * reader to begin parsing an XML document from any valid input
+     * source (a character stream, a byte stream, or a URI).</p>
+     *
+     * <p>Applications may not invoke this method while a parse is in
+     * progress (they should create a new XMLReader instead for each
+     * nested XML document).  Once a parse is complete, an
+     * application may reuse the same XMLReader object, possibly with a
+     * different input source.
+     * Configuration of the XMLReader object (such as handler bindings and
+     * values established for feature flags and properties) is unchanged
+     * by completion of a parse, unless the definition of that aspect of
+     * the configuration explicitly specifies other behavior.
+     * (For example, feature flags or properties exposing
+     * characteristics of the document being parsed.)
+     * </p>
+     *
+     * <p>During the parse, the XMLReader will provide information
+     * about the XML document through the registered event
+     * handlers.</p>
+     *
+     * <p>This method is synchronous: it will not return until parsing
+     * has ended.  If a client application wants to terminate 
+     * parsing early, it should throw an exception.</p>
+     *
+     * @param input The input source for the top-level of the
+     *        XML document.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @exception java.io.IOException An IO exception from the parser,
+     *            possibly from a byte stream or character stream
+     *            supplied by the application.
+     * @see org.xml.sax.InputSource
+     * @see #parse(java.lang.String)
+     * @see #setEntityResolver
+     * @see #setDTDHandler
+     * @see #setContentHandler
+     * @see #setErrorHandler 
+     */
+    public void parse (InputSource input)
+       throws IOException, SAXException;
+
+
+    /**
+     * Parse an XML document from a system identifier (URI).
+     *
+     * <p>This method is a shortcut for the common case of reading a
+     * document from a system identifier.  It is the exact
+     * equivalent of the following:</p>
+     *
+     * <pre>
+     * parse(new InputSource(systemId));
+     * </pre>
+     *
+     * <p>If the system identifier is a URL, it must be fully resolved
+     * by the application before it is passed to the parser.</p>
+     *
+     * @param systemId The system identifier (URI).
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @exception java.io.IOException An IO exception from the parser,
+     *            possibly from a byte stream or character stream
+     *            supplied by the application.
+     * @see #parse(org.xml.sax.InputSource)
+     */
+    public void parse (String systemId)
+       throws IOException, SAXException;
+
+}
diff --git a/src/org/xml/sax/demo/ByteStreamDemo.java b/src/org/xml/sax/demo/ByteStreamDemo.java
new file mode 100644 (file)
index 0000000..5da34b9
--- /dev/null
@@ -0,0 +1,78 @@
+package org.xml.sax.demo;
+// SAX demonstration for parsing from a raw byte stream.
+// No warranty; no copyright -- use this as you will.
+// $Id: ByteStreamDemo.java,v 1.4 1998/05/01 20:38:19 david Exp $
+
+import org.xml.sax.Parser;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import org.xml.sax.helpers.ParserFactory;
+
+import java.io.FileInputStream;
+
+
+
+/**
+  * Demonstrate parsing from a byte stream.
+  *
+  * <p>Usage: java -Dorg.xml.sax.parser=<var>CLASSNAME</var> ByteStreamDemo
+  * <var>FILE</var></p>
+  *
+  * <p>The SAX parser will open a byte stream to the file name
+  * provided.  Note the use of the InputStreamAdapter class to allow a
+  * Java InputStream to serve as a SAX ByteStream.</p>
+  *
+  * @see DemoHandler
+  */
+public class ByteStreamDemo {
+
+
+  /**
+    * Main entry point.
+    */
+  public static void main (String args[])
+    throws Exception
+  {
+    Parser parser;
+    InputSource source;
+    DemoHandler handler;
+    FileInputStream input;
+
+                               // First, check the command-line usage.
+    if (args.length != 1) {
+      System.err.println("Usage: java -Dorg.xml.sax.parser=<classname> " +
+                        "SystemIdDemo <document>");
+      System.exit(2);
+    }
+
+                               // Allocate a SAX Parser object, using
+                               // the class name provided in the
+                               // org.xml.sax.parser property.
+    parser = ParserFactory.makeParser();
+
+                               // Allocate an event handler, and register
+                               // it with the SAX parser for all four
+                               // types of events (we could use a 
+                               // separate object for each).
+    handler = new DemoHandler();
+    parser.setEntityResolver(handler);
+    parser.setDTDHandler(handler);
+    parser.setDocumentHandler(handler);
+    parser.setErrorHandler(handler);
+
+                               // Create a Java FileInputStream from
+                               // the file: note that SAX cannot
+                               // use this directly.
+    input = new FileInputStream(args[0]);
+
+                               // Create the input source.
+    source = new InputSource(input);
+    source.setSystemId(args[0]);
+
+                               // Parse the document from the
+                               // ByteStream.
+    parser.parse(source);
+  }
+
+}
diff --git a/src/org/xml/sax/demo/CharacterStreamDemo.java b/src/org/xml/sax/demo/CharacterStreamDemo.java
new file mode 100644 (file)
index 0000000..8d97f0f
--- /dev/null
@@ -0,0 +1,88 @@
+package org.xml.sax.demo;
+// SAX demonstration for parsing from a UTF-16 character stream.
+// No warranty; no copyright -- use this as you will.
+// $Id: CharacterStreamDemo.java,v 1.3 1998/05/01 20:44:33 david Exp $
+
+import org.xml.sax.Parser;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import org.xml.sax.helpers.ParserFactory;
+
+import java.io.StringReader;
+
+
+/**
+  * Demonstrate parsing from a UTF-16 character stream.
+  *
+  * <p>Usage: java -Dorg.xml.sax.parser=<var>CLASSNAME</var>
+  * CharacterStreamDemo</p>
+  *
+  * <p>The SAX parser will read the document from a character
+  * stream connected to an internal string.</p>
+  *
+  * <p>Note that the Java implementation of SAX represents a
+  * character stream using a Reader.</p>
+  *
+  * @see DemoHandler
+  * @see org.xml.org.xml.sax.parser
+  * @see org.xml.sax.InputSource
+  * @see org.xml.sax.helpers.ParserFactory
+  * @see java.io.StringReader
+  */
+public class CharacterStreamDemo {
+
+                               // This is the document, in all its glory.
+                               // In a read-world application, this
+                               // could come from a database, a text
+                               // field, or just about anywhere.
+  final static String doc = "<?xml version=\"1.0\"?>" +
+                            "<doc>\n" +
+                            "<title>Hello</title>\n" +
+                            "<para>Hello, world!</para>\n" +
+                            "</doc>\n";
+
+  /**
+    * Main entry point for an application.
+    */
+  public static void main (String args[])
+    throws Exception
+  {
+    DemoHandler handler;
+    InputSource source;
+    Parser parser;
+    StringReader reader;
+
+                               // First, check the command-line
+                               // arguments.
+    if (args.length != 0) {
+      System.err.println("Usage: java CharTest");
+      System.exit(2);
+    }
+
+                               // Allocate a SAX Parser object, using
+                               // the class name provided in the
+                               // org.xml.sax.parser property (you could
+                               // also specify a classname here).
+    parser = ParserFactory.makeParser();
+
+                               // Allocate an event handler, and register
+                               // it with the SAX parser for all four
+                               // types of events (we could use a 
+                               // separate object for each).
+    handler = new DemoHandler();
+    parser.setEntityResolver(handler);
+    parser.setDTDHandler(handler);
+    parser.setDocumentHandler(handler);
+    parser.setErrorHandler(handler);
+
+                               // Create a Java-specific StringReader
+                               // for the internal document.
+    reader = new StringReader(doc);
+
+                               // Parse the document from the
+                               // character stream.
+    parser.parse(new InputSource(reader));
+  }
+
+}
diff --git a/src/org/xml/sax/demo/DemoHandler.java b/src/org/xml/sax/demo/DemoHandler.java
new file mode 100644 (file)
index 0000000..66c6a08
--- /dev/null
@@ -0,0 +1,325 @@
+package org.xml.sax.demo;
+
+// SAX event handler for demos.
+// No warranty; no copyright -- use this as you will.
+// $Id: DemoHandler.java,v 1.3 1998/05/01 20:45:16 david Exp $
+
+import org.xml.sax.HandlerBase;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.AttributeList;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.DocumentHandler;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXParseException;
+
+
+/**
+  * Event handler class for SAX demos.
+  *
+  * <p>This handler simply reports all of the events that it receives.
+  * It is useful for testing and comparing SAX implementations, and
+  * for teaching or learning about SAX.  This is also a demonstration
+  * of how one class can implement all four handler interfaces.</p>
+  *
+  * @see org.xml.sax.EntityResolver
+  * @see org.xml.sax.DTDHandler
+  * @see org.xml.sax.DocumentHandler
+  * @see org.xml.sax.ErrorHandler
+  */
+public class DemoHandler
+  implements EntityResolver, DTDHandler, DocumentHandler, ErrorHandler
+{
+
+
+\f
+  //////////////////////////////////////////////////////////////////////
+  // Implementation of org.xml.sax.EntityResolver
+  //////////////////////////////////////////////////////////////////////
+
+
+  /**
+    * Display requests for entity resolution.
+    *
+    * <p>The SAX parser will invoke this method to give the application
+    * a chance to resolve entities.  This implementation always
+    * returns null, so that the parser will resolve the entity
+    * itself.</p>
+    *
+    * @see org.xml.sax.EntityResolver#resolveEntity
+    */
+  @Override
+       public InputSource resolveEntity (String publicId, String systemId)
+  {
+    System.out.print("Resolve entity:");
+    if (publicId != null) {
+      System.out.print(" publicId=\"" + publicId + '"');
+    }
+    System.out.println(" systemId=\"" + systemId + '"');
+
+    return null;
+  }
+
+
+\f
+  //////////////////////////////////////////////////////////////////////
+  // Implementation of org.xml.sax.DTDHandler
+  //////////////////////////////////////////////////////////////////////
+
+
+  /**
+    * Display notation declarations as they are reported.
+    *
+    * @see org.xml.sax.DTDHandler#notationDecl
+    */
+  @Override
+       public void notationDecl (String name, String publicId, String systemId)
+  {
+    System.out.print("Notation declaration: " + name);
+    if (publicId != null) {
+      System.out.print(" publicId=\"" + publicId + '"');
+    }
+    if (systemId != null) {
+      System.out.print(" systemId=\"" + systemId + '"');
+    }
+    System.out.print('\n');
+  }
+
+
+  /**
+    * Display unparsed entity declarations as they are reported.
+    *
+    * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+    */
+  @Override
+       public void unparsedEntityDecl (String name,
+                                 String publicId,
+                                 String systemId,
+                                 String notationName)
+  {
+    System.out.print("Unparsed Entity Declaration: " + name);
+    if (publicId != null) {
+      System.out.print(" publicId=\"" + publicId + '"');
+    }
+    if (systemId != null) {
+      System.out.print(" systemId=\"" + systemId + '"');
+    }
+    System.out.println(" notationName=\"" + notationName + '"');
+  }
+
+
+\f
+  //////////////////////////////////////////////////////////////////////
+  // Implementation of org.xml.sax.DocumentHandler
+  //////////////////////////////////////////////////////////////////////
+
+
+  /**
+    * Print a message when the parser provides a locator.
+    *
+    * <p>Not all SAX parsers will provide a locator object.</p>
+    *
+    * @see org.xml.sax.DocumentHandler#setDocumentLocator
+    */
+  @Override
+       public void setDocumentLocator (Locator locator)
+  {
+    System.out.println("Document locator supplied.");
+  }
+
+
+  /**
+    * Print a message at the start of the document.
+    *
+    * @see org.xml.sax.DocumentHandler#startDocument
+    */
+  @Override
+       public void startDocument ()
+  {
+    System.out.println("Start document");
+  }
+
+
+  /**
+    * Print a message for the end of the document.
+    *
+    * @see org.xml.sax.DocumentHandler#endDocument
+    */
+  @Override
+       public void endDocument ()
+  {
+    System.out.println("End document");
+  }
+
+
+  /**
+    * Print a message for the start of an element.
+    *
+    * <p>Display all attributes on separate lines, indented.</p>
+    *
+    * @see org.xml.sax.DocumentHandler#startElement
+    */
+  @Override
+       public void startElement (String name, AttributeList attributes)
+  {
+    System.out.println("Start element: " + name);
+    for (int i = 0; i < attributes.getLength(); i++) {
+      System.out.println("  Attribute: " +
+                        attributes.getName(i) +
+                        ' ' +
+                        attributes.getType(i) +
+                        " \"" +
+                        attributes.getValue(i) +
+                        '"');
+    }
+  }
+
+
+  /**
+    * Print a message for the end of an element.
+    *
+    * @see org.xml.sax.DocumentHandler#endElement
+    */
+  @Override
+       public void endElement (String name)
+  {
+    System.out.println("End element: " + name);
+  }
+
+
+  /**
+    * Print a message for character data.
+    *
+    * @see org.xml.sax.DocumentHandler#characters
+    */
+  @Override
+       public void characters (char ch[], int start, int length)
+  {
+    System.out.print("Characters: ");
+    display(ch, start, length);
+  }
+
+
+  /**
+    * Print a message for ignorable whitespace.
+    *
+    * @see org.xml.sax.DocumentHandler#ignorableWhitespace
+    */
+  @Override
+       public void ignorableWhitespace (char ch[], int start, int length)
+  {
+    System.out.print("Ignorable Whitespace: ");
+    display(ch, start, length);
+  }
+
+
+  /**
+    * Print a message for a processing instruction.
+    *
+    * @see org.xml.sax.DocumentHandler#processingInstruction
+    */
+  @Override
+       public void processingInstruction (String target, String data)
+  {
+    System.out.println("Processing instruction: " + target + ' ' + data);
+  }
+
+
+\f
+  //////////////////////////////////////////////////////////////////////
+  // Implementation of org.xml.sax.ErrorHandler
+  //////////////////////////////////////////////////////////////////////
+
+
+  /**
+    * Report all warnings, and continue parsing.
+    *
+    * @see org.xml.sax.ErrorHandler#warning
+    */
+  @Override
+       public void warning (SAXParseException exception)
+  {
+    System.out.println("Warning: " +
+                      exception.getMessage() +
+                      " (" +
+                      exception.getSystemId() +
+                      ':' +
+                      exception.getLineNumber() +
+                      ',' +
+                      exception.getColumnNumber() +
+                      ')');
+  }
+
+
+  /**
+    * Report all recoverable errors, and try to continue parsing.
+    *
+    * @see org.xml.sax.ErrorHandler#error
+    */
+  @Override
+       public void error (SAXParseException exception)
+  {
+    System.out.println("Recoverable Error: " +
+                      exception.getMessage() +
+                      " (" +
+                      exception.getSystemId() +
+                      ':' +
+                      exception.getLineNumber() +
+                      ',' +
+                      exception.getColumnNumber() +
+                      ')');
+  }
+
+
+  /**
+    * Report all fatal errors, and try to continue parsing.
+    *
+    * <p>Note: results are no longer reliable once a fatal error has
+    * been reported.</p>
+    *
+    * @see org.xml.sax.ErrorHandler#fatalError
+    */
+  @Override
+       public void fatalError (SAXParseException exception)
+  {
+    System.out.println("Fatal Error: " +
+                      exception.getMessage() +
+                      " (" +
+                      exception.getSystemId() +
+                      ':' +
+                      exception.getLineNumber() +
+                      ',' +
+                      exception.getColumnNumber() +
+                      ')');
+  }
+
+
+\f
+  //////////////////////////////////////////////////////////////////////
+  // Utility routines.
+  //////////////////////////////////////////////////////////////////////
+
+
+  /**
+    * Display text, escaping some characters.
+    */
+  private static void display (char ch[], int start, int length)
+  {
+    for (int i = start; i < start + length; i++) {
+      switch (ch[i]) {
+      case '\n':
+       System.out.print("\\n");
+       break;
+      case '\t':
+       System.out.print("\\t");
+       break;
+      default:
+       System.out.print(ch[i]);
+       break;
+      }
+    }
+    System.out.print("\n");
+  }
+
+}
diff --git a/src/org/xml/sax/demo/EntityDemo.java b/src/org/xml/sax/demo/EntityDemo.java
new file mode 100644 (file)
index 0000000..be3948c
--- /dev/null
@@ -0,0 +1,128 @@
+package org.xml.sax.demo;
+// SAX demonstration for custom entity resolution.
+// No warranty; no copyright -- use this as you will.
+// $Id: EntityDemo.java,v 1.2 1998/05/01 20:52:16 david Exp $
+
+import org.xml.sax.InputSource;
+import org.xml.sax.Parser;
+import org.xml.sax.SAXException;
+
+import org.xml.sax.helpers.ParserFactory;
+
+import java.io.StringReader;
+import java.net.URL;
+
+
+/**
+  * Demonstrate custom entity resolution.
+  *
+  * <p>Usage: java -Dorg.xml.sax.parser=<var>classname</var> EntityDemo
+  * <var>systemId</var></p>
+  *
+  * <p>If you create an XML document which references an 
+  * external text entity with the public identifier
+  * "-//megginson//TEXT Sample Entity//EN", this application will
+  * substitute the string "Entity resolution works!" for the
+  * entity's contents:</p>
+  *
+  * <pre>
+  * &lt;!DOCTYPE doc [
+  *   &lt;!ENTITY ent 
+  *     PUBLIC "-//megginson//TEXT Sample Entity//EN" "ent.xml">
+  * ]>
+  * &lt;doc>
+  * &lt;para>&ent;&lt;/para>
+  * &lt;/doc>
+  * </pre>
+  *
+  * <p>The SAX parser will open a connection to the URI itself.</p>
+  *
+  * @see DemoHandler
+  */
+public class EntityDemo extends DemoHandler {
+
+                               // This is the Reader that will be
+                               // substituted for the entity contents.
+  StringReader reader =
+    new StringReader("Entity resolution works!");
+
+  /**
+    * Main entry point.
+    */
+  public static void main (String args[])
+    throws Exception
+  {
+    Parser parser;
+    EntityDemo handler;
+
+                               // Check the command-line usage.
+    if (args.length != 1) {
+      System.err.println("Usage: java -Dorg.xml.sax.parser=<classname> " +
+                        "EntityDemo <document>");
+      System.exit(2);
+    }
+
+                               // Make the parser, using the value
+                               // provided in the org.xml.sax.parser property.
+    parser = ParserFactory.makeParser();
+
+                               // Create an event handler, and register
+                               // it with the SAX parser.
+    handler = new EntityDemo();
+    parser.setEntityResolver(handler);
+    parser.setDTDHandler(handler);
+    parser.setDocumentHandler(handler);
+    parser.setErrorHandler(handler);
+
+                               // Parse the document.
+    parser.parse(makeAbsoluteURL(args[0]));
+  }
+
+
+  /**
+    * Override resolveEntity().
+    *
+    * <p>If the public identifier is "-//megginson//TEXT Sample
+    * //Entity//EN", instruct the parser to read the entity's
+    * contents from the StringReader rather than from the 
+    * system identifier.</p>
+    *
+    * <p>The public identifier is safer than the system identifier,
+    * since the parser may have resolved the system identifier to
+    * an absolute URL.</p>
+    *
+    * @see org.xml.sax.EntityResolver#resolveEntity
+    */
+  @Override
+       public InputSource resolveEntity (String publicId, String systemId)
+  {
+    if (publicId != null &&
+       publicId.equals("-//megginson//TEXT Sample Entity//EN")) {
+      return new InputSource(reader);
+    } else {
+      return null;
+    }
+  }
+
+
+  /**
+    * If a URL is relative, make it absolute against the current directory.
+    */
+  private static String makeAbsoluteURL (String url)
+    throws java.net.MalformedURLException
+  {
+    URL baseURL;
+
+    String currentDirectory = System.getProperty("user.dir");
+    String fileSep = System.getProperty("file.separator");
+    String file = currentDirectory.replace(fileSep.charAt(0), '/') + '/';
+
+    if (file.charAt(0) != '/') {
+      file = "/" + file;
+    }
+    baseURL = new URL("file", null, file);
+
+    return new URL(baseURL, url).toString();
+  }
+
+}
diff --git a/src/org/xml/sax/ext/Attributes2.java b/src/org/xml/sax/ext/Attributes2.java
new file mode 100644 (file)
index 0000000..cb1d679
--- /dev/null
@@ -0,0 +1,132 @@
+// Attributes2.java - extended Attributes
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: Attributes2.java,v 1.6 2004/03/08 13:01:00 dmegginson Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.Attributes;
+
+
+/**
+ * SAX2 extension to augment the per-attribute information
+ * provided though {@link Attributes}.
+ * If an implementation supports this extension, the attributes
+ * provided in {@link org.xml.sax.ContentHandler#startElement
+ * ContentHandler.startElement() } will implement this interface,
+ * and the <em>http://xml.org/sax/features/use-attributes2</em>
+ * feature flag will have the value <em>true</em>.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p> XMLReader implementations are not required to support this
+ * information, and it is not part of core-only SAX2 distributions.</p>
+ *
+ * <p>Note that if an attribute was defaulted (<em>!isSpecified()</em>)
+ * it will of necessity also have been declared (<em>isDeclared()</em>)
+ * in the DTD.
+ * Similarly if an attribute's type is anything except CDATA, then it
+ * must have been declared.
+ * </p>
+ *
+ * @since SAX 2.0 (extensions 1.1 alpha)
+ * @author David Brownell
+ * @version TBS
+ */
+public interface Attributes2 extends Attributes
+{
+    /**
+     * Returns false unless the attribute was declared in the DTD.
+     * This helps distinguish two kinds of attributes that SAX reports
+     * as CDATA:  ones that were declared (and hence are usually valid),
+     * and those that were not (and which are never valid).
+     *
+     * @param index The attribute index (zero-based).
+     * @return true if the attribute was declared in the DTD,
+     *         false otherwise.
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not identify an attribute.
+     */
+    public boolean isDeclared (int index);
+
+    /**
+     * Returns false unless the attribute was declared in the DTD.
+     * This helps distinguish two kinds of attributes that SAX reports
+     * as CDATA:  ones that were declared (and hence are usually valid),
+     * and those that were not (and which are never valid).
+     *
+     * @param qName The XML qualified (prefixed) name.
+     * @return true if the attribute was declared in the DTD,
+     *         false otherwise.
+     * @exception java.lang.IllegalArgumentException When the
+     *            supplied name does not identify an attribute.
+     */
+    public boolean isDeclared (String qName);
+
+    /**
+     * Returns false unless the attribute was declared in the DTD.
+     * This helps distinguish two kinds of attributes that SAX reports
+     * as CDATA:  ones that were declared (and hence are usually valid),
+     * and those that were not (and which are never valid).
+     *
+     * <p>Remember that since DTDs do not "understand" namespaces, the
+     * namespace URI associated with an attribute may not have come from
+     * the DTD.  The declaration will have applied to the attribute's
+     * <em>qName</em>.
+     *
+     * @param uri The Namespace URI, or the empty string if
+     *        the name has no Namespace URI.
+     * @param localName The attribute's local name.
+     * @return true if the attribute was declared in the DTD,
+     *         false otherwise.
+     * @exception java.lang.IllegalArgumentException When the
+     *            supplied names do not identify an attribute.
+     */
+    public boolean isDeclared (String uri, String localName);
+
+    /**
+     * Returns true unless the attribute value was provided
+     * by DTD defaulting.
+     *
+     * @param index The attribute index (zero-based).
+     * @return true if the value was found in the XML text,
+     *         false if the value was provided by DTD defaulting.
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not identify an attribute.
+     */
+    public boolean isSpecified (int index);
+
+    /**
+     * Returns true unless the attribute value was provided
+     * by DTD defaulting.
+     *
+     * <p>Remember that since DTDs do not "understand" namespaces, the
+     * namespace URI associated with an attribute may not have come from
+     * the DTD.  The declaration will have applied to the attribute's
+     * <em>qName</em>.
+     *
+     * @param uri The Namespace URI, or the empty string if
+     *        the name has no Namespace URI.
+     * @param localName The attribute's local name.
+     * @return true if the value was found in the XML text,
+     *         false if the value was provided by DTD defaulting.
+     * @exception java.lang.IllegalArgumentException When the
+     *            supplied names do not identify an attribute.
+     */
+    public boolean isSpecified (String uri, String localName);
+
+    /**
+     * Returns true unless the attribute value was provided
+     * by DTD defaulting.
+     *
+     * @param qName The XML qualified (prefixed) name.
+     * @return true if the value was found in the XML text,
+     *         false if the value was provided by DTD defaulting.
+     * @exception java.lang.IllegalArgumentException When the
+     *            supplied name does not identify an attribute.
+     */
+    public boolean isSpecified (String qName);
+}
diff --git a/src/org/xml/sax/ext/Attributes2Impl.java b/src/org/xml/sax/ext/Attributes2Impl.java
new file mode 100644 (file)
index 0000000..ad24c78
--- /dev/null
@@ -0,0 +1,310 @@
+// Attributes2Impl.java - extended AttributesImpl
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: Attributes2Impl.java,v 1.5 2004/03/08 13:01:01 dmegginson Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+
+/**
+ * SAX2 extension helper for additional Attributes information,
+ * implementing the {@link Attributes2} interface.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p>This is not part of core-only SAX2 distributions.</p>
+ *
+ * <p>The <em>specified</em> flag for each attribute will always
+ * be true, unless it has been set to false in the copy constructor
+ * or using {@link #setSpecified}.
+ * Similarly, the <em>declared</em> flag for each attribute will
+ * always be false, except for defaulted attributes (<em>specified</em>
+ * is false), non-CDATA attributes, or when it is set to true using
+ * {@link #setDeclared}.
+ * If you change an attribute's type by hand, you may need to modify
+ * its <em>declared</em> flag to match. 
+ * </p>
+ *
+ * @since SAX 2.0 (extensions 1.1 alpha)
+ * @author David Brownell
+ * @version TBS
+ */
+public class Attributes2Impl extends AttributesImpl implements Attributes2
+{
+    private boolean    declared [];
+    private boolean    specified [];
+
+
+    /**
+     * Construct a new, empty Attributes2Impl object.
+     */
+    public Attributes2Impl () { }
+
+
+    /**
+     * Copy an existing Attributes or Attributes2 object.
+     * If the object implements Attributes2, values of the
+     * <em>specified</em> and <em>declared</em> flags for each
+     * attribute are copied.
+     * Otherwise the flag values are defaulted to assume no DTD was used,
+     * unless there is evidence to the contrary (such as attributes with
+     * type other than CDATA, which must have been <em>declared</em>).
+     *
+     * <p>This constructor is especially useful inside a
+     * {@link org.xml.sax.ContentHandler#startElement startElement} event.</p>
+     *
+     * @param atts The existing Attributes object.
+     */
+    public Attributes2Impl (Attributes atts)
+    {
+       super (atts);
+    }
+
+
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of Attributes2
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Returns the current value of the attribute's "declared" flag.
+     */
+    // javadoc mostly from interface
+    @Override
+               public boolean isDeclared (int index)
+    {
+       if (index < 0 || index >= getLength ())
+           throw new ArrayIndexOutOfBoundsException (
+               "No attribute at index: " + index);
+       return declared [index];
+    }
+
+
+    /**
+     * Returns the current value of the attribute's "declared" flag.
+     */
+    // javadoc mostly from interface
+    @Override
+               public boolean isDeclared (String uri, String localName)
+    {
+       int index = getIndex (uri, localName);
+
+       if (index < 0)
+           throw new IllegalArgumentException (
+               "No such attribute: local=" + localName
+               + ", namespace=" + uri);
+       return declared [index];
+    }
+
+
+    /**
+     * Returns the current value of the attribute's "declared" flag.
+     */
+    // javadoc mostly from interface
+    @Override
+               public boolean isDeclared (String qName)
+    {
+       int index = getIndex (qName);
+
+       if (index < 0)
+           throw new IllegalArgumentException (
+               "No such attribute: " + qName);
+       return declared [index];
+    }
+
+
+    /**
+     * Returns the current value of an attribute's "specified" flag.
+     *
+     * @param index The attribute index (zero-based).
+     * @return current flag value
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not identify an attribute.
+     */
+    @Override
+               public boolean isSpecified (int index)
+    {
+       if (index < 0 || index >= getLength ())
+           throw new ArrayIndexOutOfBoundsException (
+               "No attribute at index: " + index);
+       return specified [index];
+    }
+
+
+    /**
+     * Returns the current value of an attribute's "specified" flag.
+     *
+     * @param uri The Namespace URI, or the empty string if
+     *        the name has no Namespace URI.
+     * @param localName The attribute's local name.
+     * @return current flag value
+     * @exception java.lang.IllegalArgumentException When the
+     *            supplied names do not identify an attribute.
+     */
+    @Override
+               public boolean isSpecified (String uri, String localName)
+    {
+       int index = getIndex (uri, localName);
+
+       if (index < 0)
+           throw new IllegalArgumentException (
+               "No such attribute: local=" + localName
+               + ", namespace=" + uri);
+       return specified [index];
+    }
+
+
+    /**
+     * Returns the current value of an attribute's "specified" flag.
+     *
+     * @param qName The XML qualified (prefixed) name.
+     * @return current flag value
+     * @exception java.lang.IllegalArgumentException When the
+     *            supplied name does not identify an attribute.
+     */
+    @Override
+               public boolean isSpecified (String qName)
+    {
+       int index = getIndex (qName);
+
+       if (index < 0)
+           throw new IllegalArgumentException (
+               "No such attribute: " + qName);
+       return specified [index];
+    }
+
+
+    ////////////////////////////////////////////////////////////////////
+    // Manipulators
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Copy an entire Attributes object.  The "specified" flags are
+     * assigned as true, and "declared" flags as false (except when
+     * an attribute's type is not CDATA),
+     * unless the object is an Attributes2 object.
+     * In that case those flag values are all copied.
+     *
+     * @see AttributesImpl#setAttributes
+     */
+    @Override
+               public void setAttributes (Attributes atts)
+    {
+       int length = atts.getLength ();
+
+       super.setAttributes (atts);
+       declared = new boolean [length];
+       specified = new boolean [length];
+
+       if (atts instanceof Attributes2) {
+           Attributes2 a2 = (Attributes2) atts;
+           for (int i = 0; i < length; i++) {
+               declared [i] = a2.isDeclared (i);
+               specified [i] = a2.isSpecified (i);
+           }
+       } else {
+           for (int i = 0; i < length; i++) {
+               declared [i] = !"CDATA".equals (atts.getType (i));
+               specified [i] = true;
+           }
+       }
+    }
+
+
+    /**
+     * Add an attribute to the end of the list, setting its
+     * "specified" flag to true.  To set that flag's value
+     * to false, use {@link #setSpecified}.
+     *
+     * <p>Unless the attribute <em>type</em> is CDATA, this attribute
+     * is marked as being declared in the DTD.  To set that flag's value
+     * to true for CDATA attributes, use {@link #setDeclared}.
+     *
+     * @see AttributesImpl#addAttribute
+     */
+    @Override
+               public void addAttribute (String uri, String localName, String qName,
+                             String type, String value)
+    {
+       super.addAttribute (uri, localName, qName, type, value);
+
+       int length = getLength ();
+
+       if (length < specified.length) {
+           boolean     newFlags [];
+
+           newFlags = new boolean [length];
+           System.arraycopy (declared, 0, newFlags, 0, declared.length);
+           declared = newFlags;
+
+           newFlags = new boolean [length];
+           System.arraycopy (specified, 0, newFlags, 0, specified.length);
+           specified = newFlags;
+       }
+
+       specified [length - 1] = true;
+       declared [length - 1] = !"CDATA".equals (type);
+    }
+
+
+    // javadoc entirely from superclass
+    @Override
+               public void removeAttribute (int index)
+    {
+       int origMax = getLength () - 1;
+
+       super.removeAttribute (index);
+       if (index != origMax) {
+           System.arraycopy (declared, index + 1, declared, index,
+                   origMax - index);
+           System.arraycopy (specified, index + 1, specified, index,
+                   origMax - index);
+       }
+    }
+
+
+    /**
+     * Assign a value to the "declared" flag of a specific attribute.
+     * This is normally needed only for attributes of type CDATA,
+     * including attributes whose type is changed to or from CDATA.
+     *
+     * @param index The index of the attribute (zero-based).
+     * @param value The desired flag value.
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not identify an attribute.
+     * @see #setType
+     */
+    public void setDeclared (int index, boolean value)
+    {
+       if (index < 0 || index >= getLength ())
+           throw new ArrayIndexOutOfBoundsException (
+               "No attribute at index: " + index);
+       declared [index] = value;
+    }
+
+
+    /**
+     * Assign a value to the "specified" flag of a specific attribute.
+     * This is the only way this flag can be cleared, except clearing
+     * by initialization with the copy constructor.
+     *
+     * @param index The index of the attribute (zero-based).
+     * @param value The desired flag value.
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not identify an attribute.
+     */
+    public void setSpecified (int index, boolean value)
+    {
+       if (index < 0 || index >= getLength ())
+           throw new ArrayIndexOutOfBoundsException (
+               "No attribute at index: " + index);
+       specified [index] = value;
+    }
+}
diff --git a/src/org/xml/sax/ext/DeclHandler.java b/src/org/xml/sax/ext/DeclHandler.java
new file mode 100644 (file)
index 0000000..865e33c
--- /dev/null
@@ -0,0 +1,146 @@
+// DeclHandler.java - Optional handler for DTD declaration events.
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: DeclHandler.java,v 1.6 2004/04/22 13:28:49 dmegginson Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.SAXException;
+
+
+/**
+ * SAX2 extension handler for DTD declaration events.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This is an optional extension handler for SAX2 to provide more
+ * complete information about DTD declarations in an XML document.
+ * XML readers are not required to recognize this handler, and it
+ * is not part of core-only SAX2 distributions.</p>
+ *
+ * <p>Note that data-related DTD declarations (unparsed entities and
+ * notations) are already reported through the {@link
+ * org.xml.sax.DTDHandler DTDHandler} interface.</p>
+ *
+ * <p>If you are using the declaration handler together with a lexical
+ * handler, all of the events will occur between the
+ * {@link org.xml.sax.ext.LexicalHandler#startDTD startDTD} and the
+ * {@link org.xml.sax.ext.LexicalHandler#endDTD endDTD} events.</p>
+ *
+ * <p>To set the DeclHandler for an XML reader, use the
+ * {@link org.xml.sax.XMLReader#setProperty setProperty} method
+ * with the property name
+ * <code>http://xml.org/sax/properties/declaration-handler</code>
+ * and an object implementing this interface (or null) as the value.
+ * If the reader does not report declaration events, it will throw a
+ * {@link org.xml.sax.SAXNotRecognizedException SAXNotRecognizedException}
+ * when you attempt to register the handler.</p>
+ *
+ * @since SAX 2.0 (extensions 1.0)
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ */
+public interface DeclHandler
+{
+
+    /**
+     * Report an element type declaration.
+     *
+     * <p>The content model will consist of the string "EMPTY", the
+     * string "ANY", or a parenthesised group, optionally followed
+     * by an occurrence indicator.  The model will be normalized so
+     * that all parameter entities are fully resolved and all whitespace 
+     * is removed,and will include the enclosing parentheses.  Other
+     * normalization (such as removing redundant parentheses or 
+     * simplifying occurrence indicators) is at the discretion of the
+     * parser.</p>
+     *
+     * @param name The element type name.
+     * @param model The content model as a normalized string.
+     * @exception SAXException The application may raise an exception.
+     */
+    public abstract void elementDecl (String name, String model)
+       throws SAXException;
+
+
+    /**
+     * Report an attribute type declaration.
+     *
+     * <p>Only the effective (first) declaration for an attribute will
+     * be reported.  The type will be one of the strings "CDATA",
+     * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
+     * "ENTITIES", a parenthesized token group with 
+     * the separator "|" and all whitespace removed, or the word
+     * "NOTATION" followed by a space followed by a parenthesized
+     * token group with all whitespace removed.</p>
+     *
+     * <p>The value will be the value as reported to applications,
+     * appropriately normalized and with entity and character
+     * references expanded.  </p>
+     *
+     * @param eName The name of the associated element.
+     * @param aName The name of the attribute.
+     * @param type A string representing the attribute type.
+     * @param mode A string representing the attribute defaulting mode
+     *        ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
+     *        none of these applies.
+     * @param value A string representing the attribute's default value,
+     *        or null if there is none.
+     * @exception SAXException The application may raise an exception.
+     */
+    public abstract void attributeDecl (String eName,
+                                       String aName,
+                                       String type,
+                                       String mode,
+                                       String value)
+       throws SAXException;
+
+
+    /**
+     * Report an internal entity declaration.
+     *
+     * <p>Only the effective (first) declaration for each entity
+     * will be reported.  All parameter entities in the value
+     * will be expanded, but general entities will not.</p>
+     *
+     * @param name The name of the entity.  If it is a parameter
+     *        entity, the name will begin with '%'.
+     * @param value The replacement text of the entity.
+     * @exception SAXException The application may raise an exception.
+     * @see #externalEntityDecl
+     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+     */
+    public abstract void internalEntityDecl (String name, String value)
+       throws SAXException;
+
+
+    /**
+     * Report a parsed external entity declaration.
+     *
+     * <p>Only the effective (first) declaration for each entity
+     * will be reported.</p>
+     *
+     * <p>If the system identifier is a URL, the parser must resolve it
+     * fully before passing it to the application.</p>
+     *
+     * @param name The name of the entity.  If it is a parameter
+     *        entity, the name will begin with '%'.
+     * @param publicId The entity's public identifier, or null if none
+     *        was given.
+     * @param systemId The entity's system identifier.
+     * @exception SAXException The application may raise an exception.
+     * @see #internalEntityDecl
+     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+     */
+    public abstract void externalEntityDecl (String name, String publicId,
+                                            String systemId)
+       throws SAXException;
+
+}
+
+// end of DeclHandler.java
diff --git a/src/org/xml/sax/ext/DefaultHandler2.java b/src/org/xml/sax/ext/DefaultHandler2.java
new file mode 100644 (file)
index 0000000..f2096ad
--- /dev/null
@@ -0,0 +1,144 @@
+// DefaultHandler2.java - extended DefaultHandler
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: DefaultHandler2.java,v 1.3 2002/01/12 19:04:19 dbrownell Exp $
+
+package org.xml.sax.ext;
+
+import java.io.IOException;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+
+/**
+ * This class extends the SAX2 base handler class to support the
+ * SAX2 {@link LexicalHandler}, {@link DeclHandler}, and
+ * {@link EntityResolver2} extensions.  Except for overriding the
+ * original SAX1 {@link DefaultHandler#resolveEntity resolveEntity()}
+ * method the added handler methods just return.  Subclassers may
+ * override everything on a method-by-method basis.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p> <em>Note:</em> this class might yet learn that the
+ * <em>ContentHandler.setDocumentLocator()</em> call might be passed a
+ * {@link Locator2} object, and that the
+ * <em>ContentHandler.startElement()</em> call might be passed a
+ * {@link Attributes2} object.
+ *
+ * @since SAX 2.0 (extensions 1.1 alpha)
+ * @author David Brownell
+ * @version TBS
+ */
+public class DefaultHandler2 extends DefaultHandler
+    implements LexicalHandler, DeclHandler, EntityResolver2
+{
+    /** Constructs a handler which ignores all parsing events. */
+    public DefaultHandler2 () { }
+
+
+    // SAX2 ext-1.0 LexicalHandler
+
+    @Override
+               public void startCDATA ()
+    throws SAXException
+       {}
+
+    @Override
+               public void endCDATA ()
+    throws SAXException
+       {}
+
+    @Override
+               public void startDTD (String name, String publicId, String systemId)
+    throws SAXException
+       {}
+
+    @Override
+               public void endDTD ()
+    throws SAXException
+       {}
+
+    @Override
+               public void startEntity (String name)
+    throws SAXException
+       {}
+
+    @Override
+               public void endEntity (String name)
+    throws SAXException
+       {}
+
+    @Override
+               public void comment (char ch [], int start, int length)
+    throws SAXException
+       { }
+
+
+    // SAX2 ext-1.0 DeclHandler
+
+    @Override
+               public void attributeDecl (String eName, String aName,
+           String type, String mode, String value)
+    throws SAXException
+       {}
+
+    @Override
+               public void elementDecl (String name, String model)
+    throws SAXException
+       {}
+
+    @Override
+               public void externalEntityDecl (String name,
+       String publicId, String systemId)
+    throws SAXException
+       {}
+
+    @Override
+               public void internalEntityDecl (String name, String value)
+    throws SAXException
+       {}
+
+    // SAX2 ext-1.1 EntityResolver2
+
+    /**
+     * Tells the parser that if no external subset has been declared
+     * in the document text, none should be used.
+     */
+    @Override
+               public InputSource getExternalSubset (String name, String baseURI)
+    throws SAXException, IOException
+       { return null; }
+
+    /**
+     * Tells the parser to resolve the systemId against the baseURI
+     * and read the entity text from that resulting absolute URI.
+     * Note that because the older
+     * {@link DefaultHandler#resolveEntity DefaultHandler.resolveEntity()},
+     * method is overridden to call this one, this method may sometimes 
+     * be invoked with null <em>name</em> and <em>baseURI</em>, and
+     * with the <em>systemId</em> already absolutized.
+     */
+    @Override
+               public InputSource resolveEntity (String name, String publicId,
+           String baseURI, String systemId)
+    throws SAXException, IOException
+       { return null; }
+    
+    // SAX1 EntityResolver
+
+    /**
+     * Invokes
+     * {@link EntityResolver2#resolveEntity EntityResolver2.resolveEntity()}
+     * with null entity name and base URI.
+     * You only need to override that method to use this class.
+     */
+    @Override
+               public InputSource resolveEntity (String publicId, String systemId)
+    throws SAXException, IOException
+       { return resolveEntity (null, publicId, null, systemId); }
+}
diff --git a/src/org/xml/sax/ext/EntityResolver2.java b/src/org/xml/sax/ext/EntityResolver2.java
new file mode 100644 (file)
index 0000000..a1108a3
--- /dev/null
@@ -0,0 +1,197 @@
+// EntityResolver2.java - Extended SAX entity resolver.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: EntityResolver2.java,v 1.2 2002/01/12 19:20:08 dbrownell Exp $
+
+package org.xml.sax.ext;
+
+import java.io.IOException;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.SAXException;
+
+
+/**
+ * Extended interface for mapping external entity references to input
+ * sources, or providing a missing external subset.  The
+ * {@link XMLReader#setEntityResolver XMLReader.setEntityResolver()} method
+ * is used to provide implementations of this interface to parsers.
+ * When a parser uses the methods in this interface, the
+ * {@link EntityResolver2#resolveEntity EntityResolver2.resolveEntity()}
+ * method (in this interface) is used <em>instead of</em> the older (SAX 1.0)
+ * {@link EntityResolver#resolveEntity EntityResolver.resolveEntity()} method.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p>If a SAX application requires the customized handling which this
+ * interface defines for external entities, it must ensure that it uses
+ * an XMLReader with the
+ * <em>http://xml.org/sax/features/use-entity-resolver2</em> feature flag
+ * set to <em>true</em> (which is its default value when the feature is
+ * recognized).  If that flag is unrecognized, or its value is false,
+ * or the resolver does not implement this interface, then only the
+ * {@link EntityResolver} method will be used.
+ * </p>
+ *
+ * <p>That supports three categories of application that modify entity
+ * resolution.  <em>Old Style</em> applications won't know about this interface;
+ * they will provide an EntityResolver.
+ * <em>Transitional Mode</em> provide an EntityResolver2 and automatically
+ * get the benefit of its methods in any systems (parsers or other tools)
+ * supporting it, due to polymorphism.
+ * Both <em>Old Style</em> and <em>Transitional Mode</em> applications will
+ * work with any SAX2 parser.
+ * <em>New style</em> applications will fail to run except on SAX2 parsers
+ * that support this particular feature.
+ * They will insist that feature flag have a value of "true", and the
+ * EntityResolver2 implementation they provide  might throw an exception
+ * if the original SAX 1.0 style entity resolution method is invoked.
+ * </p>
+ *
+ * @see org.xml.sax.XMLReader#setEntityResolver
+ *
+ * @since SAX 2.0 (extensions 1.1 alpha)
+ * @author David Brownell
+ * @version TBD
+ */
+public interface EntityResolver2 extends EntityResolver
+{
+    /**
+     * Allows applications to provide an external subset for documents
+     * that don't explicitly define one.  Documents with DOCTYPE declarations
+     * that omit an external subset can thus augment the declarations
+     * available for validation, entity processing, and attribute processing
+     * (normalization, defaulting, and reporting types including ID).
+     * This augmentation is reported
+     * through the {@link LexicalHandler#startDTD startDTD()} method as if
+     * the document text had originally included the external subset;
+     * this callback is made before any internal subset data or errors
+     * are reported.</p>
+     *
+     * <p>This method can also be used with documents that have no DOCTYPE
+     * declaration.  When the root element is encountered,
+     * but no DOCTYPE declaration has been seen, this method is
+     * invoked.  If it returns a value for the external subset, that root
+     * element is declared to be the root element, giving the effect of
+     * splicing a DOCTYPE declaration at the end the prolog of a document
+     * that could not otherwise be valid.  The sequence of parser callbacks
+     * in that case logically resembles this:</p>
+     *
+     * <pre>
+     * ... comments and PIs from the prolog (as usual)
+     * startDTD ("rootName", source.getPublicId (), source.getSystemId ());
+     * startEntity ("[dtd]");
+     * ... declarations, comments, and PIs from the external subset
+     * endEntity ("[dtd]");
+     * endDTD ();
+     * ... then the rest of the document (as usual)
+     * startElement (..., "rootName", ...);
+     * </pre>
+     *
+     * <p>Note that the InputSource gets no further resolution.
+     * Implementations of this method may wish to invoke
+     * {@link #resolveEntity resolveEntity()} to gain benefits such as use
+     * of local caches of DTD entities.  Also, this method will never be
+     * used by a (non-validating) processor that is not including external
+     * parameter entities. </p>
+     *
+     * <p>Uses for this method include facilitating data validation when
+     * interoperating with XML processors that would always require
+     * undesirable network accesses for external entities, or which for
+     * other reasons adopt a "no DTDs" policy.
+     * Non-validation motives include forcing documents to include DTDs so
+     * that attributes are handled consistently.
+     * For example, an XPath processor needs to know which attibutes have
+     * type "ID" before it can process a widely used type of reference.</p>
+     * 
+     * <p><strong>Warning:</strong> Returning an external subset modifies
+     * the input document.  By providing definitions for general entities,
+     * it can make a malformed document appear to be well formed.
+     * </p>
+     *
+     * @param name Identifies the document root element.  This name comes
+     * from a DOCTYPE declaration (where available) or from the actual
+     * root element. 
+     * @param baseURI The document's base URI, serving as an additional
+     * hint for selecting the external subset.  This is always an absolute
+     * URI, unless it is null because the XMLReader was given an InputSource
+     * without one.
+     *
+     * @return An InputSource object describing the new external subset
+     * to be used by the parser, or null to indicate that no external
+     * subset is provided.
+     *
+     * @exception SAXException Any SAX exception, possibly wrapping
+     * another exception.
+     * @exception IOException Probably indicating a failure to create
+     * a new InputStream or Reader, or an illegal URL.
+     */
+    public InputSource getExternalSubset (String name, String baseURI)
+    throws SAXException, IOException;
+
+    /**
+     * Allows applications to map references to external entities into input
+     * sources, or tell the parser it should use conventional URI resolution.
+     * This method is only called for external entities which have been
+     * properly declared.
+     * This method provides more flexibility than the {@link EntityResolver}
+     * interface, supporting implementations of more complex catalogue
+     * schemes such as the one defined by the <a href=
+       "http://www.oasis-open.org/committees/entity/spec-2001-08-06.html"
+       >OASIS XML Catalogs</a> specification.</p>
+     *
+     * <p>Parsers configured to use this resolver method will call it
+     * to determine the input source to use for any external entity
+     * being included because of a reference in the XML text.
+     * That excludes the document entity, and any external entity returned
+     * by {@link #getExternalSubset getExternalSubset()}.
+     * When a (non-validating) processor is configured not to include
+     * a class of entities (parameter or general) through use of feature
+     * flags, this method is not invoked for such entities.  </p>
+     *
+     * <p>Note that the entity naming scheme used here is the same one
+     * used in the {@link LexicalHandler}, or in the {@link
+       org.xml.sax.ContentHandler#skippedEntity
+       ContentHandler.skippedEntity()}
+     * method. </p>
+     *
+     * @param name Identifies the external entity being resolved.
+     * Either "[dtd]" for the external subset, or a name starting
+     * with "%" to indicate a parameter entity, or else the name of
+     * a general entity.  This is never null when invoked by a SAX2
+     * parser.
+     * @param publicId The public identifier of the external entity being
+     * referenced (normalized as required by the XML specification), or
+     * null if none was supplied.
+     * @param baseURI The URI with respect to which relative systemIDs
+     * are interpreted.  This is always an absolute URI, unless it is
+     * null (likely because the XMLReader was given an InputSource without
+     *  one).  This URI is defined by the XML specification to be the one
+     * associated with the "&lt;" starting the relevant declaration.
+     * @param systemId The system identifier of the external entity
+     * being referenced; either a relative or absolute URI.
+     *  This is never null when invoked by a SAX2 parser; only declared
+     * entities, and any external subset, are resolved by such parsers.
+     *
+     * @return An InputSource object describing the new input source to
+     * be used by the parser.  Returning null directs the parser to
+     * resolve the system ID against the base URI and open a connection
+     * to resulting URI.
+     *
+     * @exception SAXException Any SAX exception, possibly wrapping
+     * another exception.
+     * @exception IOException Probably indicating a failure to create
+     * a new InputStream or Reader, or an illegal URL.
+     */
+    public InputSource resolveEntity (
+           String name,
+           String publicId,
+           String baseURI,
+           String systemId
+    ) throws SAXException, IOException;
+}
diff --git a/src/org/xml/sax/ext/LexicalHandler.java b/src/org/xml/sax/ext/LexicalHandler.java
new file mode 100644 (file)
index 0000000..d63d87f
--- /dev/null
@@ -0,0 +1,212 @@
+// LexicalHandler.java - optional handler for lexical parse events.
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: LexicalHandler.java,v 1.5 2002/01/30 21:00:44 dbrownell Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.SAXException;
+
+/**
+ * SAX2 extension handler for lexical events.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This is an optional extension handler for SAX2 to provide
+ * lexical information about an XML document, such as comments
+ * and CDATA section boundaries.
+ * XML readers are not required to recognize this handler, and it
+ * is not part of core-only SAX2 distributions.</p>
+ *
+ * <p>The events in the lexical handler apply to the entire document,
+ * not just to the document element, and all lexical handler events
+ * must appear between the content handler's startDocument and
+ * endDocument events.</p>
+ *
+ * <p>To set the LexicalHandler for an XML reader, use the
+ * {@link org.xml.sax.XMLReader#setProperty setProperty} method
+ * with the property name
+ * <code>http://xml.org/sax/properties/lexical-handler</code>
+ * and an object implementing this interface (or null) as the value.
+ * If the reader does not report lexical events, it will throw a
+ * {@link org.xml.sax.SAXNotRecognizedException SAXNotRecognizedException}
+ * when you attempt to register the handler.</p>
+ *
+ * @since SAX 2.0 (extensions 1.0)
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ */
+public interface LexicalHandler
+{
+
+    /**
+     * Report the start of DTD declarations, if any.
+     *
+     * <p>This method is intended to report the beginning of the
+     * DOCTYPE declaration; if the document has no DOCTYPE declaration,
+     * this method will not be invoked.</p>
+     *
+     * <p>All declarations reported through 
+     * {@link org.xml.sax.DTDHandler DTDHandler} or
+     * {@link org.xml.sax.ext.DeclHandler DeclHandler} events must appear
+     * between the startDTD and {@link #endDTD endDTD} events.
+     * Declarations are assumed to belong to the internal DTD subset
+     * unless they appear between {@link #startEntity startEntity}
+     * and {@link #endEntity endEntity} events.  Comments and
+     * processing instructions from the DTD should also be reported
+     * between the startDTD and endDTD events, in their original 
+     * order of (logical) occurrence; they are not required to
+     * appear in their correct locations relative to DTDHandler
+     * or DeclHandler events, however.</p>
+     *
+     * <p>Note that the start/endDTD events will appear within
+     * the start/endDocument events from ContentHandler and
+     * before the first 
+     * {@link org.xml.sax.ContentHandler#startElement startElement}
+     * event.</p>
+     *
+     * @param name The document type name.
+     * @param publicId The declared public identifier for the
+     *        external DTD subset, or null if none was declared.
+     * @param systemId The declared system identifier for the
+     *        external DTD subset, or null if none was declared.
+     *        (Note that this is not resolved against the document
+     *        base URI.)
+     * @exception SAXException The application may raise an
+     *            exception.
+     * @see #endDTD
+     * @see #startEntity
+     */
+    public abstract void startDTD (String name, String publicId,
+                                  String systemId)
+       throws SAXException;
+
+
+    /**
+     * Report the end of DTD declarations.
+     *
+     * <p>This method is intended to report the end of the
+     * DOCTYPE declaration; if the document has no DOCTYPE declaration,
+     * this method will not be invoked.</p>
+     *
+     * @exception SAXException The application may raise an exception.
+     * @see #startDTD
+     */
+    public abstract void endDTD ()
+       throws SAXException;
+
+
+    /**
+     * Report the beginning of some internal and external XML entities.
+     *
+     * <p>The reporting of parameter entities (including
+     * the external DTD subset) is optional, and SAX2 drivers that
+     * report LexicalHandler events may not implement it; you can use the
+     * <code
+     * >http://xml.org/sax/features/lexical-handler/parameter-entities</code>
+     * feature to query or control the reporting of parameter entities.</p>
+     *
+     * <p>General entities are reported with their regular names,
+     * parameter entities have '%' prepended to their names, and 
+     * the external DTD subset has the pseudo-entity name "[dtd]".</p>
+     *
+     * <p>When a SAX2 driver is providing these events, all other 
+     * events must be properly nested within start/end entity 
+     * events.  There is no additional requirement that events from 
+     * {@link org.xml.sax.ext.DeclHandler DeclHandler} or
+     * {@link org.xml.sax.DTDHandler DTDHandler} be properly ordered.</p>
+     *
+     * <p>Note that skipped entities will be reported through the
+     * {@link org.xml.sax.ContentHandler#skippedEntity skippedEntity}
+     * event, which is part of the ContentHandler interface.</p>
+     *
+     * <p>Because of the streaming event model that SAX uses, some
+     * entity boundaries cannot be reported under any 
+     * circumstances:</p>
+     *
+     * <ul>
+     * <li>general entities within attribute values</li>
+     * <li>parameter entities within declarations</li>
+     * </ul>
+     *
+     * <p>These will be silently expanded, with no indication of where
+     * the original entity boundaries were.</p>
+     *
+     * <p>Note also that the boundaries of character references (which
+     * are not really entities anyway) are not reported.</p>
+     *
+     * <p>All start/endEntity events must be properly nested.
+     *
+     * @param name The name of the entity.  If it is a parameter
+     *        entity, the name will begin with '%', and if it is the
+     *        external DTD subset, it will be "[dtd]".
+     * @exception SAXException The application may raise an exception.
+     * @see #endEntity
+     * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
+     * @see org.xml.sax.ext.DeclHandler#externalEntityDecl 
+     */
+    public abstract void startEntity (String name)
+       throws SAXException;
+
+
+    /**
+     * Report the end of an entity.
+     *
+     * @param name The name of the entity that is ending.
+     * @exception SAXException The application may raise an exception.
+     * @see #startEntity
+     */
+    public abstract void endEntity (String name)
+       throws SAXException;
+
+
+    /**
+     * Report the start of a CDATA section.
+     *
+     * <p>The contents of the CDATA section will be reported through
+     * the regular {@link org.xml.sax.ContentHandler#characters
+     * characters} event; this event is intended only to report
+     * the boundary.</p>
+     *
+     * @exception SAXException The application may raise an exception.
+     * @see #endCDATA
+     */
+    public abstract void startCDATA ()
+       throws SAXException;
+
+
+    /**
+     * Report the end of a CDATA section.
+     *
+     * @exception SAXException The application may raise an exception.
+     * @see #startCDATA
+     */
+    public abstract void endCDATA ()
+       throws SAXException;
+
+
+    /**
+     * Report an XML comment anywhere in the document.
+     *
+     * <p>This callback will be used for comments inside or outside the
+     * document element, including comments in the external DTD
+     * subset (if read).  Comments in the DTD must be properly
+     * nested inside start/endDTD and start/endEntity events (if
+     * used).</p>
+     *
+     * @param ch An array holding the characters in the comment.
+     * @param start The starting position in the array.
+     * @param length The number of characters to use from the array.
+     * @exception SAXException The application may raise an exception.
+     */
+    public abstract void comment (char ch[], int start, int length)
+       throws SAXException;
+
+}
+
+// end of LexicalHandler.java
diff --git a/src/org/xml/sax/ext/Locator2.java b/src/org/xml/sax/ext/Locator2.java
new file mode 100644 (file)
index 0000000..6de9a16
--- /dev/null
@@ -0,0 +1,75 @@
+// Locator2.java - extended Locator
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: Locator2.java,v 1.5 2004/03/17 14:30:10 dmegginson Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.Locator;
+
+
+/**
+ * SAX2 extension to augment the entity information provided 
+ * though a {@link Locator}.
+ * If an implementation supports this extension, the Locator
+ * provided in {@link org.xml.sax.ContentHandler#setDocumentLocator
+ * ContentHandler.setDocumentLocator() } will implement this
+ * interface, and the
+ * <em>http://xml.org/sax/features/use-locator2</em> feature
+ * flag will have the value <em>true</em>.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p> XMLReader implementations are not required to support this
+ * information, and it is not part of core-only SAX2 distributions.</p>
+ *
+ * @since SAX 2.0 (extensions 1.1 alpha)
+ * @author David Brownell
+ * @version TBS
+ */
+public interface Locator2 extends Locator
+{
+    /**
+     * Returns the version of XML used for the entity.  This will
+     * normally be the identifier from the current entity's
+     * <em>&lt;?xml&nbsp;version='...'&nbsp;...?&gt;</em> declaration,
+     * or be defaulted by the parser.
+     *
+     * @return Identifier for the XML version being used to interpret
+     * the entity's text, or null if that information is not yet
+     * available in the current parsing state.
+     */
+    public String getXMLVersion ();
+
+    /**
+     * Returns the name of the character encoding for the entity.
+     * If the encoding was declared externally (for example, in a MIME
+     * Content-Type header), that will be the name returned.  Else if there
+     * was an <em>&lt;?xml&nbsp;...encoding='...'?&gt;</em> declaration at
+     * the start of the document, that encoding name will be returned.
+     * Otherwise the encoding will been inferred (normally to be UTF-8, or
+     * some UTF-16 variant), and that inferred name will be returned.
+     *
+     * <p>When an {@link org.xml.sax.InputSource InputSource} is used
+     * to provide an entity's character stream, this method returns the
+     * encoding provided in that input stream.
+     *
+     * <p> Note that some recent W3C specifications require that text
+     * in some encodings be normalized, using Unicode Normalization
+     * Form C, before processing.  Such normalization must be performed
+     * by applications, and would normally be triggered based on the
+     * value returned by this method.
+     *
+     * <p> Encoding names may be those used by the underlying JVM,
+     * and comparisons should be case-insensitive.
+     *
+     * @return Name of the character encoding being used to interpret
+     * * the entity's text, or null if this was not provided for a *
+     * character stream passed through an InputSource or is otherwise
+     * not yet available in the current parsing state.
+     */
+    public String getEncoding ();
+}
diff --git a/src/org/xml/sax/ext/Locator2Impl.java b/src/org/xml/sax/ext/Locator2Impl.java
new file mode 100644 (file)
index 0000000..89ea4ba
--- /dev/null
@@ -0,0 +1,103 @@
+// Locator2Impl.java - extended LocatorImpl
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: Locator2Impl.java,v 1.3 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.Locator;
+import org.xml.sax.helpers.LocatorImpl;
+
+
+/**
+ * SAX2 extension helper for holding additional Entity information,
+ * implementing the {@link Locator2} interface.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p> This is not part of core-only SAX2 distributions.</p>
+ *
+ * @since SAX 2.0.2
+ * @author David Brownell
+ * @version TBS
+ */
+public class Locator2Impl extends LocatorImpl implements Locator2
+{
+    private String     encoding;
+    private String     version;
+
+
+    /**
+     * Construct a new, empty Locator2Impl object.
+     * This will not normally be useful, since the main purpose
+     * of this class is to make a snapshot of an existing Locator.
+     */
+    public Locator2Impl () { }
+
+    /**
+     * Copy an existing Locator or Locator2 object.
+     * If the object implements Locator2, values of the
+     * <em>encoding</em> and <em>version</em>strings are copied,
+     * otherwise they set to <em>null</em>. 
+     *
+     * @param locator The existing Locator object.
+     */
+    public Locator2Impl (Locator locator)
+    {
+       super (locator);
+       if (locator instanceof Locator2) {
+           Locator2    l2 = (Locator2) locator;
+
+           version = l2.getXMLVersion ();
+           encoding = l2.getEncoding ();
+       }
+    }
+
+    ////////////////////////////////////////////////////////////////////
+    // Locator2 method implementations
+    ////////////////////////////////////////////////////////////////////
+    
+    /**
+     * Returns the current value of the version property.
+     *
+     * @see #setXMLVersion
+     */
+    @Override
+               public String getXMLVersion ()
+       { return version; }
+
+    /**
+     * Returns the current value of the encoding property.
+     *
+     * @see #setEncoding
+     */
+    @Override
+               public String getEncoding ()
+       { return encoding; }
+
+
+    ////////////////////////////////////////////////////////////////////
+    // Setters 
+    ////////////////////////////////////////////////////////////////////
+    
+    /**
+     * Assigns the current value of the version property.
+     *
+     * @param version the new "version" value
+     * @see #getXMLVersion
+     */
+    public void setXMLVersion (String version)
+       { this.version = version; }
+
+    /**
+     * Assigns the current value of the encoding property.
+     *
+     * @param encoding the new "encoding" value
+     * @see #getEncoding
+     */
+    public void setEncoding (String encoding)
+       { this.encoding = encoding; }
+}
diff --git a/src/org/xml/sax/helpers/AttributeListImpl.java b/src/org/xml/sax/helpers/AttributeListImpl.java
new file mode 100644 (file)
index 0000000..fba22ea
--- /dev/null
@@ -0,0 +1,318 @@
+// SAX default implementation for AttributeList.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: AttributeListImpl.java,v 1.6 2002/01/30 20:52:22 dbrownell Exp $
+
+package org.xml.sax.helpers;
+
+import org.xml.sax.AttributeList;
+
+import java.util.Vector;
+
+
+/**e
+ * Default implementation for AttributeList.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>AttributeList implements the deprecated SAX1 {@link
+ * org.xml.sax.AttributeList AttributeList} interface, and has been
+ * replaced by the new SAX2 {@link org.xml.sax.helpers.AttributesImpl
+ * AttributesImpl} interface.</p>
+ *
+ * <p>This class provides a convenience implementation of the SAX
+ * {@link org.xml.sax.AttributeList AttributeList} interface.  This 
+ * implementation is useful both for SAX parser writers, who can use 
+ * it to provide attributes to the application, and for SAX application 
+ * writers, who can use it to create a persistent copy of an element's 
+ * attribute specifications:</p>
+ *
+ * <pre>
+ * private AttributeList myatts;
+ *
+ * public void startElement (String name, AttributeList atts)
+ * {
+ *              // create a persistent copy of the attribute list
+ *              // for use outside this method
+ *   myatts = new AttributeListImpl(atts);
+ *   [...]
+ * }
+ * </pre>
+ *
+ * <p>Please note that SAX parsers are not required to use this
+ * class to provide an implementation of AttributeList; it is
+ * supplied only as an optional convenience.  In particular, 
+ * parser writers are encouraged to invent more efficient
+ * implementations.</p>
+ *
+ * @deprecated This class implements a deprecated interface,
+ *             {@link org.xml.sax.AttributeList AttributeList};
+ *             that interface has been replaced by
+ *             {@link org.xml.sax.Attributes Attributes},
+ *             which is implemented in the
+ *             {@link org.xml.sax.helpers.AttributesImpl 
+ *            AttributesImpl} helper class.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.AttributeList
+ * @see org.xml.sax.DocumentHandler#startElement 
+ */
+public class AttributeListImpl implements AttributeList
+{
+    
+    /**
+     * Create an empty attribute list.
+     *
+     * <p>This constructor is most useful for parser writers, who
+     * will use it to create a single, reusable attribute list that
+     * can be reset with the clear method between elements.</p>
+     *
+     * @see #addAttribute
+     * @see #clear
+     */
+    public AttributeListImpl ()
+    {
+    }
+    
+    
+    /**
+     * Construct a persistent copy of an existing attribute list.
+     *
+     * <p>This constructor is most useful for application writers,
+     * who will use it to create a persistent copy of an existing
+     * attribute list.</p>
+     *
+     * @param atts The attribute list to copy
+     * @see org.xml.sax.DocumentHandler#startElement
+     */
+    public AttributeListImpl (AttributeList atts)
+    {
+       setAttributeList(atts);
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Methods specific to this class.
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Set the attribute list, discarding previous contents.
+     *
+     * <p>This method allows an application writer to reuse an
+     * attribute list easily.</p>
+     *
+     * @param atts The attribute list to copy.
+     */
+    public void setAttributeList (AttributeList atts)
+    {
+       int count = atts.getLength();
+       
+       clear();
+       
+       for (int i = 0; i < count; i++) {
+           addAttribute(atts.getName(i), atts.getType(i), atts.getValue(i));
+       }
+    }
+    
+    
+    /**
+     * Add an attribute to an attribute list.
+     *
+     * <p>This method is provided for SAX parser writers, to allow them
+     * to build up an attribute list incrementally before delivering
+     * it to the application.</p>
+     *
+     * @param name The attribute name.
+     * @param type The attribute type ("NMTOKEN" for an enumeration).
+     * @param value The attribute value (must not be null).
+     * @see #removeAttribute
+     * @see org.xml.sax.DocumentHandler#startElement
+     */
+    public void addAttribute (String name, String type, String value)
+    {
+       names.addElement(name);
+       types.addElement(type);
+       values.addElement(value);
+    }
+    
+    
+    /**
+     * Remove an attribute from the list.
+     *
+     * <p>SAX application writers can use this method to filter an
+     * attribute out of an AttributeList.  Note that invoking this
+     * method will change the length of the attribute list and
+     * some of the attribute's indices.</p>
+     *
+     * <p>If the requested attribute is not in the list, this is
+     * a no-op.</p>
+     *
+     * @param name The attribute name.
+     * @see #addAttribute
+     */
+    public void removeAttribute (String name)
+    {
+       int i = names.indexOf(name);
+       
+       if (i >= 0) {
+           names.removeElementAt(i);
+           types.removeElementAt(i);
+           values.removeElementAt(i);
+       }
+    }
+    
+    
+    /**
+     * Clear the attribute list.
+     *
+     * <p>SAX parser writers can use this method to reset the attribute
+     * list between DocumentHandler.startElement events.  Normally,
+     * it will make sense to reuse the same AttributeListImpl object
+     * rather than allocating a new one each time.</p>
+     *
+     * @see org.xml.sax.DocumentHandler#startElement
+     */
+    public void clear ()
+    {
+       names.removeAllElements();
+       types.removeAllElements();
+       values.removeAllElements();
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.AttributeList
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Return the number of attributes in the list.
+     *
+     * @return The number of attributes in the list.
+     * @see org.xml.sax.AttributeList#getLength
+     */
+    @Override
+               public int getLength ()
+    {
+       return names.size();
+    }
+    
+    
+    /**
+     * Get the name of an attribute (by position).
+     *
+     * @param i The position of the attribute in the list.
+     * @return The attribute name as a string, or null if there
+     *         is no attribute at that position.
+     * @see org.xml.sax.AttributeList#getName(int)
+     */
+    @Override
+               public String getName (int i)
+    {
+       if (i < 0) {
+           return null;
+       }
+       try {
+           return (String)names.elementAt(i);
+       } catch (ArrayIndexOutOfBoundsException e) {
+           return null;
+       }
+    }
+    
+    
+    /**
+     * Get the type of an attribute (by position).
+     *
+     * @param i The position of the attribute in the list.
+     * @return The attribute type as a string ("NMTOKEN" for an
+     *         enumeration, and "CDATA" if no declaration was
+     *         read), or null if there is no attribute at
+     *         that position.
+     * @see org.xml.sax.AttributeList#getType(int)
+     */
+    @Override
+               public String getType (int i)
+    {
+       if (i < 0) {
+           return null;
+       }
+       try {
+           return (String)types.elementAt(i);
+       } catch (ArrayIndexOutOfBoundsException e) {
+           return null;
+       }
+    }
+    
+    
+    /**
+     * Get the value of an attribute (by position).
+     *
+     * @param i The position of the attribute in the list.
+     * @return The attribute value as a string, or null if
+     *         there is no attribute at that position.
+     * @see org.xml.sax.AttributeList#getValue(int)
+     */
+    @Override
+               public String getValue (int i)
+    {
+       if (i < 0) {
+           return null;
+       }
+       try {
+           return (String)values.elementAt(i);
+       } catch (ArrayIndexOutOfBoundsException e) {
+           return null;
+       }
+    }
+    
+    
+    /**
+     * Get the type of an attribute (by name).
+     *
+     * @param name The attribute name.
+     * @return The attribute type as a string ("NMTOKEN" for an
+     *         enumeration, and "CDATA" if no declaration was
+     *         read).
+     * @see org.xml.sax.AttributeList#getType(java.lang.String)
+     */
+    @Override
+               public String getType (String name)
+    {
+       return getType(names.indexOf(name));
+    }
+    
+    
+    /**
+     * Get the value of an attribute (by name).
+     *
+     * @param name The attribute name.
+     * @see org.xml.sax.AttributeList#getValue(java.lang.String)
+     */
+    @Override
+               public String getValue (String name)
+    {
+       return getValue(names.indexOf(name));
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal state.
+    ////////////////////////////////////////////////////////////////////
+
+    Vector names = new Vector();
+    Vector types = new Vector();
+    Vector values = new Vector();
+
+}
+
+// end of AttributeListImpl.java
diff --git a/src/org/xml/sax/helpers/AttributesImpl.java b/src/org/xml/sax/helpers/AttributesImpl.java
new file mode 100644 (file)
index 0000000..fac6b26
--- /dev/null
@@ -0,0 +1,630 @@
+// AttributesImpl.java - default implementation of Attributes.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the public domain.
+// $Id: AttributesImpl.java,v 1.9 2002/01/30 20:52:24 dbrownell Exp $
+
+package org.xml.sax.helpers;
+
+import org.xml.sax.Attributes;
+
+
+/**
+ * Default implementation of the Attributes interface.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class provides a default implementation of the SAX2
+ * {@link org.xml.sax.Attributes Attributes} interface, with the 
+ * addition of manipulators so that the list can be modified or 
+ * reused.</p>
+ *
+ * <p>There are two typical uses of this class:</p>
+ *
+ * <ol>
+ * <li>to take a persistent snapshot of an Attributes object
+ *  in a {@link org.xml.sax.ContentHandler#startElement startElement} event; or</li>
+ * <li>to construct or modify an Attributes object in a SAX2 driver or filter.</li>
+ * </ol>
+ *
+ * <p>This class replaces the now-deprecated SAX1 {@link 
+ * org.xml.sax.helpers.AttributeListImpl AttributeListImpl}
+ * class; in addition to supporting the updated Attributes
+ * interface rather than the deprecated {@link org.xml.sax.AttributeList
+ * AttributeList} interface, it also includes a much more efficient 
+ * implementation using a single array rather than a set of Vectors.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ */
+public class AttributesImpl implements Attributes
+{
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Constructors.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Construct a new, empty AttributesImpl object.
+     */
+    public AttributesImpl ()
+    {
+       length = 0;
+       data = null;
+    }
+
+
+    /**
+     * Copy an existing Attributes object.
+     *
+     * <p>This constructor is especially useful inside a
+     * {@link org.xml.sax.ContentHandler#startElement startElement} event.</p>
+     *
+     * @param atts The existing Attributes object.
+     */
+    public AttributesImpl (Attributes atts)
+    {
+       setAttributes(atts);
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.Attributes.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Return the number of attributes in the list.
+     *
+     * @return The number of attributes in the list.
+     * @see org.xml.sax.Attributes#getLength
+     */
+    @Override
+               public int getLength ()
+    {
+       return length;
+    }
+
+
+    /**
+     * Return an attribute's Namespace URI.
+     *
+     * @param index The attribute's index (zero-based).
+     * @return The Namespace URI, the empty string if none is
+     *         available, or null if the index is out of range.
+     * @see org.xml.sax.Attributes#getURI
+     */
+    @Override
+               public String getURI (int index)
+    {
+       if (index >= 0 && index < length) {
+           return data[index*5];
+       } else {
+           return null;
+       }
+    }
+
+
+    /**
+     * Return an attribute's local name.
+     *
+     * @param index The attribute's index (zero-based).
+     * @return The attribute's local name, the empty string if 
+     *         none is available, or null if the index if out of range.
+     * @see org.xml.sax.Attributes#getLocalName
+     */
+    @Override
+               public String getLocalName (int index)
+    {
+       if (index >= 0 && index < length) {
+           return data[index*5+1];
+       } else {
+           return null;
+       }
+    }
+
+
+    /**
+     * Return an attribute's qualified (prefixed) name.
+     *
+     * @param index The attribute's index (zero-based).
+     * @return The attribute's qualified name, the empty string if 
+     *         none is available, or null if the index is out of bounds.
+     * @see org.xml.sax.Attributes#getQName
+     */
+    @Override
+               public String getQName (int index)
+    {
+       if (index >= 0 && index < length) {
+           return data[index*5+2];
+       } else {
+           return null;
+       }
+    }
+
+
+    /**
+     * Return an attribute's type by index.
+     *
+     * @param index The attribute's index (zero-based).
+     * @return The attribute's type, "CDATA" if the type is unknown, or null
+     *         if the index is out of bounds.
+     * @see org.xml.sax.Attributes#getType(int)
+     */
+    @Override
+               public String getType (int index)
+    {
+       if (index >= 0 && index < length) {
+           return data[index*5+3];
+       } else {
+           return null;
+       }
+    }
+
+
+    /**
+     * Return an attribute's value by index.
+     *
+     * @param index The attribute's index (zero-based).
+     * @return The attribute's value or null if the index is out of bounds.
+     * @see org.xml.sax.Attributes#getValue(int)
+     */
+    @Override
+               public String getValue (int index)
+    {
+       if (index >= 0 && index < length) {
+           return data[index*5+4];
+       } else {
+           return null;
+       }
+    }
+
+
+    /**
+     * Look up an attribute's index by Namespace name.
+     *
+     * <p>In many cases, it will be more efficient to look up the name once and
+     * use the index query methods rather than using the name query methods
+     * repeatedly.</p>
+     *
+     * @param uri The attribute's Namespace URI, or the empty
+     *        string if none is available.
+     * @param localName The attribute's local name.
+     * @return The attribute's index, or -1 if none matches.
+     * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
+     */
+    @Override
+               public int getIndex (String uri, String localName)
+    {
+       int max = length * 5;
+       for (int i = 0; i < max; i += 5) {
+           if (data[i].equals(uri) && data[i+1].equals(localName)) {
+               return i / 5;
+           }
+       } 
+       return -1;
+    }
+
+
+    /**
+     * Look up an attribute's index by qualified (prefixed) name.
+     *
+     * @param qName The qualified name.
+     * @return The attribute's index, or -1 if none matches.
+     * @see org.xml.sax.Attributes#getIndex(java.lang.String)
+     */
+    @Override
+               public int getIndex (String qName)
+    {
+       int max = length * 5;
+       for (int i = 0; i < max; i += 5) {
+           if (data[i+2].equals(qName)) {
+               return i / 5;
+           }
+       } 
+       return -1;
+    }
+
+
+    /**
+     * Look up an attribute's type by Namespace-qualified name.
+     *
+     * @param uri The Namespace URI, or the empty string for a name
+     *        with no explicit Namespace URI.
+     * @param localName The local name.
+     * @return The attribute's type, or null if there is no
+     *         matching attribute.
+     * @see org.xml.sax.Attributes#getType(java.lang.String,java.lang.String)
+     */
+    @Override
+               public String getType (String uri, String localName)
+    {
+       int max = length * 5;
+       for (int i = 0; i < max; i += 5) {
+           if (data[i].equals(uri) && data[i+1].equals(localName)) {
+               return data[i+3];
+           }
+       } 
+       return null;
+    }
+
+
+    /**
+     * Look up an attribute's type by qualified (prefixed) name.
+     *
+     * @param qName The qualified name.
+     * @return The attribute's type, or null if there is no
+     *         matching attribute.
+     * @see org.xml.sax.Attributes#getType(java.lang.String)
+     */
+    @Override
+               public String getType (String qName)
+    {
+       int max = length * 5;
+       for (int i = 0; i < max; i += 5) {
+           if (data[i+2].equals(qName)) {
+               return data[i+3];
+           }
+       }
+       return null;
+    }
+
+
+    /**
+     * Look up an attribute's value by Namespace-qualified name.
+     *
+     * @param uri The Namespace URI, or the empty string for a name
+     *        with no explicit Namespace URI.
+     * @param localName The local name.
+     * @return The attribute's value, or null if there is no
+     *         matching attribute.
+     * @see org.xml.sax.Attributes#getValue(java.lang.String,java.lang.String)
+     */
+    @Override
+               public String getValue (String uri, String localName)
+    {
+       int max = length * 5;
+       for (int i = 0; i < max; i += 5) {
+           if (data[i].equals(uri) && data[i+1].equals(localName)) {
+               return data[i+4];
+           }
+       }
+       return null;
+    }
+
+
+    /**
+     * Look up an attribute's value by qualified (prefixed) name.
+     *
+     * @param qName The qualified name.
+     * @return The attribute's value, or null if there is no
+     *         matching attribute.
+     * @see org.xml.sax.Attributes#getValue(java.lang.String)
+     */
+    @Override
+               public String getValue (String qName)
+    {
+       int max = length * 5;
+       for (int i = 0; i < max; i += 5) {
+           if (data[i+2].equals(qName)) {
+               return data[i+4];
+           }
+       }
+       return null;
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Manipulators.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Clear the attribute list for reuse.
+     *
+     * <p>Note that little memory is freed by this call:
+     * the current array is kept so it can be 
+     * reused.</p>
+     */
+    public void clear ()
+    {
+       if (data != null) {
+           for (int i = 0; i < (length * 5); i++)
+               data [i] = null;
+       }
+       length = 0;
+    }
+
+
+    /**
+     * Copy an entire Attributes object.
+     *
+     * <p>It may be more efficient to reuse an existing object
+     * rather than constantly allocating new ones.</p>
+     * 
+     * @param atts The attributes to copy.
+     */
+    public void setAttributes (Attributes atts)
+    {
+        clear();
+        length = atts.getLength();
+        if (length > 0) {
+            data = new String[length*5];
+            for (int i = 0; i < length; i++) {
+                data[i*5] = atts.getURI(i);
+                data[i*5+1] = atts.getLocalName(i);
+                data[i*5+2] = atts.getQName(i);
+                data[i*5+3] = atts.getType(i);
+                data[i*5+4] = atts.getValue(i);
+            }
+       }
+    }
+
+
+    /**
+     * Add an attribute to the end of the list.
+     *
+     * <p>For the sake of speed, this method does no checking
+     * to see if the attribute is already in the list: that is
+     * the responsibility of the application.</p>
+     *
+     * @param uri The Namespace URI, or the empty string if
+     *        none is available or Namespace processing is not
+     *        being performed.
+     * @param localName The local name, or the empty string if
+     *        Namespace processing is not being performed.
+     * @param qName The qualified (prefixed) name, or the empty string
+     *        if qualified names are not available.
+     * @param type The attribute type as a string.
+     * @param value The attribute value.
+     */
+    public void addAttribute (String uri, String localName, String qName,
+                             String type, String value)
+    {
+       ensureCapacity(length+1);
+       data[length*5] = uri;
+       data[length*5+1] = localName;
+       data[length*5+2] = qName;
+       data[length*5+3] = type;
+       data[length*5+4] = value;
+       length++;
+    }
+
+
+    /**
+     * Set an attribute in the list.
+     *
+     * <p>For the sake of speed, this method does no checking
+     * for name conflicts or well-formedness: such checks are the
+     * responsibility of the application.</p>
+     *
+     * @param index The index of the attribute (zero-based).
+     * @param uri The Namespace URI, or the empty string if
+     *        none is available or Namespace processing is not
+     *        being performed.
+     * @param localName The local name, or the empty string if
+     *        Namespace processing is not being performed.
+     * @param qName The qualified name, or the empty string
+     *        if qualified names are not available.
+     * @param type The attribute type as a string.
+     * @param value The attribute value.
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not point to an attribute
+     *            in the list.
+     */
+    public void setAttribute (int index, String uri, String localName,
+                             String qName, String type, String value)
+    {
+       if (index >= 0 && index < length) {
+           data[index*5] = uri;
+           data[index*5+1] = localName;
+           data[index*5+2] = qName;
+           data[index*5+3] = type;
+           data[index*5+4] = value;
+       } else {
+           badIndex(index);
+       }
+    }
+
+
+    /**
+     * Remove an attribute from the list.
+     *
+     * @param index The index of the attribute (zero-based).
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not point to an attribute
+     *            in the list.
+     */
+    public void removeAttribute (int index)
+    {
+       if (index >= 0 && index < length) {
+           if (index < length - 1) {
+               System.arraycopy(data, (index+1)*5, data, index*5,
+                                (length-index-1)*5);
+           }
+           index = (length - 1) * 5;
+           data [index++] = null;
+           data [index++] = null;
+           data [index++] = null;
+           data [index++] = null;
+           data [index] = null;
+           length--;
+       } else {
+           badIndex(index);
+       }
+    }
+
+
+    /**
+     * Set the Namespace URI of a specific attribute.
+     *
+     * @param index The index of the attribute (zero-based).
+     * @param uri The attribute's Namespace URI, or the empty
+     *        string for none.
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not point to an attribute
+     *            in the list.
+     */
+    public void setURI (int index, String uri)
+    {
+       if (index >= 0 && index < length) {
+           data[index*5] = uri;
+       } else {
+           badIndex(index);
+       }
+    }
+
+
+    /**
+     * Set the local name of a specific attribute.
+     *
+     * @param index The index of the attribute (zero-based).
+     * @param localName The attribute's local name, or the empty
+     *        string for none.
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not point to an attribute
+     *            in the list.
+     */
+    public void setLocalName (int index, String localName)
+    {
+       if (index >= 0 && index < length) {
+           data[index*5+1] = localName;
+       } else {
+           badIndex(index);
+       }
+    }
+
+
+    /**
+     * Set the qualified name of a specific attribute.
+     *
+     * @param index The index of the attribute (zero-based).
+     * @param qName The attribute's qualified name, or the empty
+     *        string for none.
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not point to an attribute
+     *            in the list.
+     */
+    public void setQName (int index, String qName)
+    {
+       if (index >= 0 && index < length) {
+           data[index*5+2] = qName;
+       } else {
+           badIndex(index);
+       }
+    }
+
+
+    /**
+     * Set the type of a specific attribute.
+     *
+     * @param index The index of the attribute (zero-based).
+     * @param type The attribute's type.
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not point to an attribute
+     *            in the list.
+     */
+    public void setType (int index, String type)
+    {
+       if (index >= 0 && index < length) {
+           data[index*5+3] = type;
+       } else {
+           badIndex(index);
+       }
+    }
+
+
+    /**
+     * Set the value of a specific attribute.
+     *
+     * @param index The index of the attribute (zero-based).
+     * @param value The attribute's value.
+     * @exception java.lang.ArrayIndexOutOfBoundsException When the
+     *            supplied index does not point to an attribute
+     *            in the list.
+     */
+    public void setValue (int index, String value)
+    {
+       if (index >= 0 && index < length) {
+           data[index*5+4] = value;
+       } else {
+           badIndex(index);
+       }
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal methods.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Ensure the internal array's capacity.
+     *
+     * @param n The minimum number of attributes that the array must
+     *        be able to hold.
+     */
+    private void ensureCapacity (int n)    {
+        if (n <= 0) {
+            return;
+        }
+        int max;
+        if (data == null || data.length == 0) {
+            max = 25;
+        }
+        else if (data.length >= n * 5) {
+            return;
+        }
+        else {
+            max = data.length;
+        }
+        while (max < n * 5) {
+            max *= 2;
+        }
+
+        String newData[] = new String[max];
+        if (length > 0) {
+            System.arraycopy(data, 0, newData, 0, length*5);
+        }
+        data = newData;
+    }
+
+
+    /**
+     * Report a bad array index in a manipulator.
+     *
+     * @param index The index to report.
+     * @exception java.lang.ArrayIndexOutOfBoundsException Always.
+     */
+    private void badIndex (int index)
+       throws ArrayIndexOutOfBoundsException
+    {
+       String msg =
+           "Attempt to modify attribute at illegal index: " + index;
+       throw new ArrayIndexOutOfBoundsException(msg);
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal state.
+    ////////////////////////////////////////////////////////////////////
+
+    int length;
+    String data [];
+
+}
+
+// end of AttributesImpl.java
+
diff --git a/src/org/xml/sax/helpers/DefaultHandler.java b/src/org/xml/sax/helpers/DefaultHandler.java
new file mode 100644 (file)
index 0000000..83bde62
--- /dev/null
@@ -0,0 +1,484 @@
+// DefaultHandler.java - default implementation of the core handlers.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the public domain.
+// $Id: DefaultHandler.java,v 1.9 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.helpers;
+
+import java.io.IOException;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.Attributes;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+
+/**
+ * Default base class for SAX2 event handlers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class is available as a convenience base class for SAX2
+ * applications: it provides default implementations for all of the
+ * callbacks in the four core SAX2 handler classes:</p>
+ *
+ * <ul>
+ * <li>{@link org.xml.sax.EntityResolver EntityResolver}</li>
+ * <li>{@link org.xml.sax.DTDHandler DTDHandler}</li>
+ * <li>{@link org.xml.sax.ContentHandler ContentHandler}</li>
+ * <li>{@link org.xml.sax.ErrorHandler ErrorHandler}</li>
+ * </ul>
+ *
+ * <p>Application writers can extend this class when they need to
+ * implement only part of an interface; parser writers can
+ * instantiate this class to provide default handlers when the
+ * application has not supplied its own.</p>
+ *
+ * <p>This class replaces the deprecated SAX1
+ * {@link org.xml.sax.HandlerBase HandlerBase} class.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson,
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.EntityResolver
+ * @see org.xml.sax.DTDHandler
+ * @see org.xml.sax.ContentHandler
+ * @see org.xml.sax.ErrorHandler
+ */
+public class DefaultHandler
+    implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler
+{
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Default implementation of the EntityResolver interface.
+    ////////////////////////////////////////////////////////////////////
+    
+    /**
+     * Resolve an external entity.
+     *
+     * <p>Always return null, so that the parser will use the system
+     * identifier provided in the XML document.  This method implements
+     * the SAX default behaviour: application writers can override it
+     * in a subclass to do special translations such as catalog lookups
+     * or URI redirection.</p>
+     *
+     * @param publicId The public identifer, or null if none is
+     *                 available.
+     * @param systemId The system identifier provided in the XML 
+     *                 document.
+     * @return The new input source, or null to require the
+     *         default behaviour.
+     * @exception java.io.IOException If there is an error setting
+     *            up the new input source.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.EntityResolver#resolveEntity
+     */
+    @Override
+               public InputSource resolveEntity (String publicId, String systemId)
+       throws IOException, SAXException
+    {
+       return null;
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Default implementation of DTDHandler interface.
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Receive notification of a notation declaration.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass if they wish to keep track of the notations
+     * declared in a document.</p>
+     *
+     * @param name The notation name.
+     * @param publicId The notation public identifier, or null if not
+     *                 available.
+     * @param systemId The notation system identifier.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.DTDHandler#notationDecl
+     */
+    @Override
+               public void notationDecl (String name, String publicId, String systemId)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of an unparsed entity declaration.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to keep track of the unparsed entities
+     * declared in a document.</p>
+     *
+     * @param name The entity name.
+     * @param publicId The entity public identifier, or null if not
+     *                 available.
+     * @param systemId The entity system identifier.
+     * @param notationName The name of the associated notation.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+     */
+    @Override
+               public void unparsedEntityDecl (String name, String publicId,
+                                   String systemId, String notationName)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Default implementation of ContentHandler interface.
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Receive a Locator object for document events.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass if they wish to store the locator for use
+     * with other document events.</p>
+     *
+     * @param locator A locator for all SAX document events.
+     * @see org.xml.sax.ContentHandler#setDocumentLocator
+     * @see org.xml.sax.Locator
+     */
+    @Override
+               public void setDocumentLocator (Locator locator)
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of the beginning of the document.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions at the beginning
+     * of a document (such as allocating the root node of a tree or
+     * creating an output file).</p>
+     *
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#startDocument
+     */
+    @Override
+               public void startDocument ()
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of the end of the document.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions at the end
+     * of a document (such as finalising a tree or closing an output
+     * file).</p>
+     *
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#endDocument
+     */
+    @Override
+               public void endDocument ()
+       throws SAXException
+    {
+       // no op
+    }
+
+
+    /**
+     * Receive notification of the start of a Namespace mapping.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions at the start of
+     * each Namespace prefix scope (such as storing the prefix mapping).</p>
+     *
+     * @param prefix The Namespace prefix being declared.
+     * @param uri The Namespace URI mapped to the prefix.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#startPrefixMapping
+     */
+    @Override
+               public void startPrefixMapping (String prefix, String uri)
+       throws SAXException
+    {
+       // no op
+    }
+
+
+    /**
+     * Receive notification of the end of a Namespace mapping.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions at the end of
+     * each prefix mapping.</p>
+     *
+     * @param prefix The Namespace prefix being declared.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#endPrefixMapping
+     */
+    @Override
+               public void endPrefixMapping (String prefix)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of the start of an element.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions at the start of
+     * each element (such as allocating a new tree node or writing
+     * output to a file).</p>
+     *
+     * @param uri The Namespace URI, or the empty string if the
+     *        element has no Namespace URI or if Namespace
+     *        processing is not being performed.
+     * @param localName The local name (without prefix), or the
+     *        empty string if Namespace processing is not being
+     *        performed.
+     * @param qName The qualified name (with prefix), or the
+     *        empty string if qualified names are not available.
+     * @param attributes The attributes attached to the element.  If
+     *        there are no attributes, it shall be an empty
+     *        Attributes object.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#startElement
+     */
+    @Override
+               public void startElement (String uri, String localName,
+                             String qName, Attributes attributes)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of the end of an element.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions at the end of
+     * each element (such as finalising a tree node or writing
+     * output to a file).</p>
+     *
+     * @param uri The Namespace URI, or the empty string if the
+     *        element has no Namespace URI or if Namespace
+     *        processing is not being performed.
+     * @param localName The local name (without prefix), or the
+     *        empty string if Namespace processing is not being
+     *        performed.
+     * @param qName The qualified name (with prefix), or the
+     *        empty string if qualified names are not available.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#endElement
+     */
+    @Override
+               public void endElement (String uri, String localName, String qName)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of character data inside an element.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method to take specific actions for each chunk of character data
+     * (such as adding the data to a node or buffer, or printing it to
+     * a file).</p>
+     *
+     * @param ch The characters.
+     * @param start The start position in the character array.
+     * @param length The number of characters to use from the
+     *               character array.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#characters
+     */
+    @Override
+               public void characters (char ch[], int start, int length)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of ignorable whitespace in element content.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method to take specific actions for each chunk of ignorable
+     * whitespace (such as adding data to a node or buffer, or printing
+     * it to a file).</p>
+     *
+     * @param ch The whitespace characters.
+     * @param start The start position in the character array.
+     * @param length The number of characters to use from the
+     *               character array.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#ignorableWhitespace
+     */
+    @Override
+               public void ignorableWhitespace (char ch[], int start, int length)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of a processing instruction.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions for each
+     * processing instruction, such as setting status variables or
+     * invoking other methods.</p>
+     *
+     * @param target The processing instruction target.
+     * @param data The processing instruction data, or null if
+     *             none is supplied.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#processingInstruction
+     */
+    @Override
+               public void processingInstruction (String target, String data)
+       throws SAXException
+    {
+       // no op
+    }
+
+
+    /**
+     * Receive notification of a skipped entity.
+     *
+     * <p>By default, do nothing.  Application writers may override this
+     * method in a subclass to take specific actions for each
+     * processing instruction, such as setting status variables or
+     * invoking other methods.</p>
+     *
+     * @param name The name of the skipped entity.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#processingInstruction
+     */
+    @Override
+               public void skippedEntity (String name)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Default implementation of the ErrorHandler interface.
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Receive notification of a parser warning.
+     *
+     * <p>The default implementation does nothing.  Application writers
+     * may override this method in a subclass to take specific actions
+     * for each warning, such as inserting the message in a log file or
+     * printing it to the console.</p>
+     *
+     * @param e The warning information encoded as an exception.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ErrorHandler#warning
+     * @see org.xml.sax.SAXParseException
+     */
+    @Override
+               public void warning (SAXParseException e)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Receive notification of a recoverable parser error.
+     *
+     * <p>The default implementation does nothing.  Application writers
+     * may override this method in a subclass to take specific actions
+     * for each error, such as inserting the message in a log file or
+     * printing it to the console.</p>
+     *
+     * @param e The warning information encoded as an exception.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ErrorHandler#warning
+     * @see org.xml.sax.SAXParseException
+     */
+    @Override
+               public void error (SAXParseException e)
+       throws SAXException
+    {
+       // no op
+    }
+    
+    
+    /**
+     * Report a fatal XML parsing error.
+     *
+     * <p>The default implementation throws a SAXParseException.
+     * Application writers may override this method in a subclass if
+     * they need to take specific actions for each fatal error (such as
+     * collecting all of the errors into a single report): in any case,
+     * the application must stop all regular processing when this
+     * method is invoked, since the document is no longer reliable, and
+     * the parser may no longer report parsing events.</p>
+     *
+     * @param e The error information encoded as an exception.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ErrorHandler#fatalError
+     * @see org.xml.sax.SAXParseException
+     */
+    @Override
+               public void fatalError (SAXParseException e)
+       throws SAXException
+    {
+       throw e;
+    }
+    
+}
+
+// end of DefaultHandler.java
diff --git a/src/org/xml/sax/helpers/LocatorImpl.java b/src/org/xml/sax/helpers/LocatorImpl.java
new file mode 100644 (file)
index 0000000..90eab7e
--- /dev/null
@@ -0,0 +1,218 @@
+// SAX default implementation for Locator.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: LocatorImpl.java,v 1.6 2002/01/30 20:52:27 dbrownell Exp $
+
+package org.xml.sax.helpers;
+
+import org.xml.sax.Locator;
+
+
+/**
+ * Provide an optional convenience implementation of Locator.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class is available mainly for application writers, who
+ * can use it to make a persistent snapshot of a locator at any
+ * point during a document parse:</p>
+ *
+ * <pre>
+ * Locator locator;
+ * Locator startloc;
+ *
+ * public void setLocator (Locator locator)
+ * {
+ *         // note the locator
+ *   this.locator = locator;
+ * }
+ *
+ * public void startDocument ()
+ * {
+ *         // save the location of the start of the document
+ *         // for future use.
+ *   Locator startloc = new LocatorImpl(locator);
+ * }
+ *</pre>
+ *
+ * <p>Normally, parser writers will not use this class, since it
+ * is more efficient to provide location information only when
+ * requested, rather than constantly updating a Locator object.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.Locator Locator
+ */
+public class LocatorImpl implements Locator
+{
+    
+    
+    /**
+     * Zero-argument constructor.
+     *
+     * <p>This will not normally be useful, since the main purpose
+     * of this class is to make a snapshot of an existing Locator.</p>
+     */
+    public LocatorImpl ()
+    {
+    }
+    
+    
+    /**
+     * Copy constructor.
+     *
+     * <p>Create a persistent copy of the current state of a locator.
+     * When the original locator changes, this copy will still keep
+     * the original values (and it can be used outside the scope of
+     * DocumentHandler methods).</p>
+     *
+     * @param locator The locator to copy.
+     */
+    public LocatorImpl (Locator locator)
+    {
+       setPublicId(locator.getPublicId());
+       setSystemId(locator.getSystemId());
+       setLineNumber(locator.getLineNumber());
+       setColumnNumber(locator.getColumnNumber());
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.Locator
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Return the saved public identifier.
+     *
+     * @return The public identifier as a string, or null if none
+     *         is available.
+     * @see org.xml.sax.Locator#getPublicId
+     * @see #setPublicId
+     */
+    @Override
+               public String getPublicId ()
+    {
+       return publicId;
+    }
+    
+    
+    /**
+     * Return the saved system identifier.
+     *
+     * @return The system identifier as a string, or null if none
+     *         is available.
+     * @see org.xml.sax.Locator#getSystemId
+     * @see #setSystemId
+     */
+    @Override
+               public String getSystemId ()
+    {
+       return systemId;
+    }
+    
+    
+    /**
+     * Return the saved line number (1-based).
+     *
+     * @return The line number as an integer, or -1 if none is available.
+     * @see org.xml.sax.Locator#getLineNumber
+     * @see #setLineNumber
+     */
+    @Override
+               public int getLineNumber ()
+    {
+       return lineNumber;
+    }
+    
+    
+    /**
+     * Return the saved column number (1-based).
+     *
+     * @return The column number as an integer, or -1 if none is available.
+     * @see org.xml.sax.Locator#getColumnNumber
+     * @see #setColumnNumber
+     */
+    @Override
+               public int getColumnNumber ()
+    {
+       return columnNumber;
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Setters for the properties (not in org.xml.sax.Locator)
+    ////////////////////////////////////////////////////////////////////
+    
+    
+    /**
+     * Set the public identifier for this locator.
+     *
+     * @param publicId The new public identifier, or null 
+     *        if none is available.
+     * @see #getPublicId
+     */
+    public void setPublicId (String publicId)
+    {
+       this.publicId = publicId;
+    }
+    
+    
+    /**
+     * Set the system identifier for this locator.
+     *
+     * @param systemId The new system identifier, or null 
+     *        if none is available.
+     * @see #getSystemId
+     */
+    public void setSystemId (String systemId)
+    {
+       this.systemId = systemId;
+    }
+    
+    
+    /**
+     * Set the line number for this locator (1-based).
+     *
+     * @param lineNumber The line number, or -1 if none is available.
+     * @see #getLineNumber
+     */
+    public void setLineNumber (int lineNumber)
+    {
+       this.lineNumber = lineNumber;
+    }
+    
+    
+    /**
+     * Set the column number for this locator (1-based).
+     *
+     * @param columnNumber The column number, or -1 if none is available.
+     * @see #getColumnNumber
+     */
+    public void setColumnNumber (int columnNumber)
+    {
+       this.columnNumber = columnNumber;
+    }
+    
+    
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal state.
+    ////////////////////////////////////////////////////////////////////
+    
+    private String publicId;
+    private String systemId;
+    private int lineNumber;
+    private int columnNumber;
+    
+}
+
+// end of LocatorImpl.java
diff --git a/src/org/xml/sax/helpers/NamespaceSupport.java b/src/org/xml/sax/helpers/NamespaceSupport.java
new file mode 100644 (file)
index 0000000..3c15f5e
--- /dev/null
@@ -0,0 +1,835 @@
+// NamespaceSupport.java - generic Namespace support for SAX.
+// http://www.saxproject.org
+// Written by David Megginson
+// This class is in the Public Domain.  NO WARRANTY!
+// $Id: NamespaceSupport.java,v 1.15 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.helpers;
+
+import java.util.EmptyStackException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+
+/**
+ * Encapsulate Namespace logic for use by applications using SAX,
+ * or internally by SAX drivers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class encapsulates the logic of Namespace processing: it
+ * tracks the declarations currently in force for each context and
+ * automatically processes qualified XML names into their Namespace
+ * parts; it can also be used in reverse for generating XML qnames
+ * from Namespaces.</p>
+ *
+ * <p>Namespace support objects are reusable, but the reset method
+ * must be invoked between each session.</p>
+ *
+ * <p>Here is a simple session:</p>
+ *
+ * <pre>
+ * String parts[] = new String[3];
+ * NamespaceSupport support = new NamespaceSupport();
+ *
+ * support.pushContext();
+ * support.declarePrefix("", "http://www.w3.org/1999/xhtml");
+ * support.declarePrefix("dc", "http://www.purl.org/dc#");
+ *
+ * parts = support.processName("p", parts, false);
+ * System.out.println("Namespace URI: " + parts[0]);
+ * System.out.println("Local name: " + parts[1]);
+ * System.out.println("Raw name: " + parts[2]);
+ *
+ * parts = support.processName("dc:title", parts, false);
+ * System.out.println("Namespace URI: " + parts[0]);
+ * System.out.println("Local name: " + parts[1]);
+ * System.out.println("Raw name: " + parts[2]);
+ *
+ * support.popContext();
+ * </pre>
+ *
+ * <p>Note that this class is optimized for the use case where most
+ * elements do not contain Namespace declarations: if the same
+ * prefix/URI mapping is repeated for each context (for example), this
+ * class will be somewhat less efficient.</p>
+ *
+ * <p>Although SAX drivers (parsers) may choose to use this class to
+ * implement namespace handling, they are not required to do so.
+ * Applications must track namespace information themselves if they
+ * want to use namespace information.
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ */
+public class NamespaceSupport
+{
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Constants.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * The XML Namespace URI as a constant.
+     * The value is <code>http://www.w3.org/XML/1998/namespace</code>
+     * as defined in the "Namespaces in XML" * recommendation.
+     *
+     * <p>This is the Namespace URI that is automatically mapped
+     * to the "xml" prefix.</p>
+     */
+    public final static String XMLNS =
+       "http://www.w3.org/XML/1998/namespace";
+
+
+    /**
+     * The namespace declaration URI as a constant.
+     * The value is <code>http://www.w3.org/xmlns/2000/</code>, as defined
+     * in a backwards-incompatible erratum to the "Namespaces in XML"
+     * recommendation.  Because that erratum postdated SAX2, SAX2 defaults 
+     * to the original recommendation, and does not normally use this URI.
+     * 
+     *
+     * <p>This is the Namespace URI that is optionally applied to
+     * <em>xmlns</em> and <em>xmlns:*</em> attributes, which are used to
+     * declare namespaces.  </p>
+     *
+     * @since SAX 2.1alpha
+     * @see #setNamespaceDeclUris
+     * @see #isNamespaceDeclUris
+     */
+    public final static String NSDECL =
+       "http://www.w3.org/xmlns/2000/";
+
+
+    /**
+     * An empty enumeration.
+     */
+    private final static Enumeration EMPTY_ENUMERATION =
+       new Vector().elements();
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Constructor.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Create a new Namespace support object.
+     */
+    public NamespaceSupport ()
+    {
+       reset();
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Context management.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Reset this Namespace support object for reuse.
+     *
+     * <p>It is necessary to invoke this method before reusing the
+     * Namespace support object for a new session.  If namespace
+     * declaration URIs are to be supported, that flag must also
+     * be set to a non-default value.
+     * </p>
+     *
+     * @see #setNamespaceDeclUris
+     */
+    public void reset ()
+    {
+       contexts = new Context[32];
+       namespaceDeclUris = false;
+       contextPos = 0;
+       contexts[contextPos] = currentContext = new Context();
+       currentContext.declarePrefix("xml", XMLNS);
+    }
+
+
+    /**
+     * Start a new Namespace context.
+     * The new context will automatically inherit
+     * the declarations of its parent context, but it will also keep
+     * track of which declarations were made within this context.
+     *
+     * <p>Event callback code should start a new context once per element.
+     * This means being ready to call this in either of two places.
+     * For elements that don't include namespace declarations, the
+     * <em>ContentHandler.startElement()</em> callback is the right place.
+     * For elements with such a declaration, it'd done in the first
+     * <em>ContentHandler.startPrefixMapping()</em> callback.
+     * A boolean flag can be used to
+     * track whether a context has been started yet.  When either of
+     * those methods is called, it checks the flag to see if a new context
+     * needs to be started.  If so, it starts the context and sets the
+     * flag.  After <em>ContentHandler.startElement()</em>
+     * does that, it always clears the flag.
+     *
+     * <p>Normally, SAX drivers would push a new context at the beginning
+     * of each XML element.  Then they perform a first pass over the
+     * attributes to process all namespace declarations, making
+     * <em>ContentHandler.startPrefixMapping()</em> callbacks.
+     * Then a second pass is made, to determine the namespace-qualified
+     * names for all attributes and for the element name.
+     * Finally all the information for the
+     * <em>ContentHandler.startElement()</em> callback is available,
+     * so it can then be made.
+     *
+     * <p>The Namespace support object always starts with a base context
+     * already in force: in this context, only the "xml" prefix is
+     * declared.</p>
+     *
+     * @see org.xml.sax.ContentHandler
+     * @see #popContext
+     */
+    public void pushContext ()
+    {
+       int max = contexts.length;
+
+       contexts [contextPos].declsOK = false;
+       contextPos++;
+
+                               // Extend the array if necessary
+       if (contextPos >= max) {
+           Context newContexts[] = new Context[max*2];
+           System.arraycopy(contexts, 0, newContexts, 0, max);
+           max *= 2;
+           contexts = newContexts;
+       }
+
+                               // Allocate the context if necessary.
+       currentContext = contexts[contextPos];
+       if (currentContext == null) {
+           contexts[contextPos] = currentContext = new Context();
+       }
+
+                               // Set the parent, if any.
+       if (contextPos > 0) {
+           currentContext.setParent(contexts[contextPos - 1]);
+       }
+    }
+
+
+    /**
+     * Revert to the previous Namespace context.
+     *
+     * <p>Normally, you should pop the context at the end of each
+     * XML element.  After popping the context, all Namespace prefix
+     * mappings that were previously in force are restored.</p>
+     *
+     * <p>You must not attempt to declare additional Namespace
+     * prefixes after popping a context, unless you push another
+     * context first.</p>
+     *
+     * @see #pushContext
+     */
+    public void popContext ()
+    {
+       contexts[contextPos].clear();
+       contextPos--;
+       if (contextPos < 0) {
+           throw new EmptyStackException();
+       }
+       currentContext = contexts[contextPos];
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Operations within a context.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Declare a Namespace prefix.  All prefixes must be declared
+     * before they are referenced.  For example, a SAX driver (parser)
+     * would scan an element's attributes
+     * in two passes:  first for namespace declarations,
+     * then a second pass using {@link #processName processName()} to
+     * interpret prefixes against (potentially redefined) prefixes.
+     *
+     * <p>This method declares a prefix in the current Namespace
+     * context; the prefix will remain in force until this context
+     * is popped, unless it is shadowed in a descendant context.</p>
+     *
+     * <p>To declare the default element Namespace, use the empty string as
+     * the prefix.</p>
+     *
+     * <p>Note that you must <em>not</em> declare a prefix after
+     * you've pushed and popped another Namespace context, or
+     * treated the declarations phase as complete by processing
+     * a prefixed name.</p>
+     *
+     * <p>Note that there is an asymmetry in this library: {@link
+     * #getPrefix getPrefix} will not return the "" prefix,
+     * even if you have declared a default element namespace.
+     * To check for a default namespace,
+     * you have to look it up explicitly using {@link #getURI getURI}.
+     * This asymmetry exists to make it easier to look up prefixes
+     * for attribute names, where the default prefix is not allowed.</p>
+     *
+     * @param prefix The prefix to declare, or the empty string to
+     * indicate the default element namespace.  This may never have
+     * the value "xml" or "xmlns".
+     * @param uri The Namespace URI to associate with the prefix.
+     * @return true if the prefix was legal, false otherwise
+     *
+     * @see #processName
+     * @see #getURI
+     * @see #getPrefix
+     */
+    public boolean declarePrefix (String prefix, String uri)
+    {
+       if (prefix.equals("xml") || prefix.equals("xmlns")) {
+           return false;
+       } else {
+           currentContext.declarePrefix(prefix, uri);
+           return true;
+       }
+    }
+
+
+    /**
+     * Process a raw XML qualified name, after all declarations in the
+     * current context have been handled by {@link #declarePrefix
+     * declarePrefix()}.
+     *
+     * <p>This method processes a raw XML qualified name in the
+     * current context by removing the prefix and looking it up among
+     * the prefixes currently declared.  The return value will be the
+     * array supplied by the caller, filled in as follows:</p>
+     *
+     * <dl>
+     * <dt>parts[0]</dt>
+     * <dd>The Namespace URI, or an empty string if none is
+     *  in use.</dd>
+     * <dt>parts[1]</dt>
+     * <dd>The local name (without prefix).</dd>
+     * <dt>parts[2]</dt>
+     * <dd>The original raw name.</dd>
+     * </dl>
+     *
+     * <p>All of the strings in the array will be internalized.  If
+     * the raw name has a prefix that has not been declared, then
+     * the return value will be null.</p>
+     *
+     * <p>Note that attribute names are processed differently than
+     * element names: an unprefixed element name will receive the
+     * default Namespace (if any), while an unprefixed attribute name
+     * will not.</p>
+     *
+     * @param qName The XML qualified name to be processed.
+     * @param parts An array supplied by the caller, capable of
+     *        holding at least three members.
+     * @param isAttribute A flag indicating whether this is an
+     *        attribute name (true) or an element name (false).
+     * @return The supplied array holding three internalized strings 
+     *        representing the Namespace URI (or empty string), the
+     *        local name, and the XML qualified name; or null if there
+     *        is an undeclared prefix.
+     * @see #declarePrefix
+     * @see java.lang.String#intern */
+    public String [] processName (String qName, String parts[],
+                                 boolean isAttribute)
+    {
+       String myParts[] = currentContext.processName(qName, isAttribute);
+       if (myParts == null) {
+           return null;
+       } else {
+           parts[0] = myParts[0];
+           parts[1] = myParts[1];
+           parts[2] = myParts[2];
+           return parts;
+       }
+    }
+
+
+    /**
+     * Look up a prefix and get the currently-mapped Namespace URI.
+     *
+     * <p>This method looks up the prefix in the current context.
+     * Use the empty string ("") for the default Namespace.</p>
+     *
+     * @param prefix The prefix to look up.
+     * @return The associated Namespace URI, or null if the prefix
+     *         is undeclared in this context.
+     * @see #getPrefix
+     * @see #getPrefixes
+     */
+    public String getURI (String prefix)
+    {
+       return currentContext.getURI(prefix);
+    }
+
+
+    /**
+     * Return an enumeration of all prefixes whose declarations are
+     * active in the current context.
+     * This includes declarations from parent contexts that have
+     * not been overridden.
+     *
+     * <p><strong>Note:</strong> if there is a default prefix, it will not be
+     * returned in this enumeration; check for the default prefix
+     * using the {@link #getURI getURI} with an argument of "".</p>
+     *
+     * @return An enumeration of prefixes (never empty).
+     * @see #getDeclaredPrefixes
+     * @see #getURI
+     */
+    public Enumeration getPrefixes ()
+    {
+       return currentContext.getPrefixes();
+    }
+
+
+    /**
+     * Return one of the prefixes mapped to a Namespace URI.
+     *
+     * <p>If more than one prefix is currently mapped to the same
+     * URI, this method will make an arbitrary selection; if you
+     * want all of the prefixes, use the {@link #getPrefixes}
+     * method instead.</p>
+     *
+     * <p><strong>Note:</strong> this will never return the empty (default) prefix;
+     * to check for a default prefix, use the {@link #getURI getURI}
+     * method with an argument of "".</p>
+     *
+     * @param uri the namespace URI
+     * @return one of the prefixes currently mapped to the URI supplied,
+     *         or null if none is mapped or if the URI is assigned to
+     *         the default namespace
+     * @see #getPrefixes(java.lang.String)
+     * @see #getURI
+     */
+    public String getPrefix (String uri)
+    {
+       return currentContext.getPrefix(uri);
+    }
+
+
+    /**
+     * Return an enumeration of all prefixes for a given URI whose
+     * declarations are active in the current context.
+     * This includes declarations from parent contexts that have
+     * not been overridden.
+     *
+     * <p>This method returns prefixes mapped to a specific Namespace
+     * URI.  The xml: prefix will be included.  If you want only one
+     * prefix that's mapped to the Namespace URI, and you don't care 
+     * which one you get, use the {@link #getPrefix getPrefix}
+     *  method instead.</p>
+     *
+     * <p><strong>Note:</strong> the empty (default) prefix is <em>never</em> included
+     * in this enumeration; to check for the presence of a default
+     * Namespace, use the {@link #getURI getURI} method with an
+     * argument of "".</p>
+     *
+     * @param uri The Namespace URI.
+     * @return An enumeration of prefixes (never empty).
+     * @see #getPrefix
+     * @see #getDeclaredPrefixes
+     * @see #getURI
+     */
+    public Enumeration getPrefixes (String uri)
+    {
+       Vector prefixes = new Vector();
+       Enumeration allPrefixes = getPrefixes();
+       while (allPrefixes.hasMoreElements()) {
+           String prefix = (String)allPrefixes.nextElement();
+           if (uri.equals(getURI(prefix))) {
+               prefixes.addElement(prefix);
+           }
+       }
+       return prefixes.elements();
+    }
+
+
+    /**
+     * Return an enumeration of all prefixes declared in this context.
+     *
+     * <p>The empty (default) prefix will be included in this 
+     * enumeration; note that this behaviour differs from that of
+     * {@link #getPrefix} and {@link #getPrefixes}.</p>
+     *
+     * @return An enumeration of all prefixes declared in this
+     *         context.
+     * @see #getPrefixes
+     * @see #getURI
+     */
+    public Enumeration getDeclaredPrefixes ()
+    {
+       return currentContext.getDeclaredPrefixes();
+    }
+
+    /**
+     * Controls whether namespace declaration attributes are placed
+     * into the {@link #NSDECL NSDECL} namespace
+     * by {@link #processName processName()}.  This may only be
+     * changed before any contexts have been pushed.
+     *
+     * @since SAX 2.1alpha
+     *
+     * @exception IllegalStateException when attempting to set this
+     * after any context has been pushed.
+     */
+    public void setNamespaceDeclUris (boolean value)
+    {
+       if (contextPos != 0)
+           throw new IllegalStateException ();
+       if (value == namespaceDeclUris)
+           return;
+       namespaceDeclUris = value;
+       if (value)
+           currentContext.declarePrefix ("xmlns", NSDECL);
+       else {
+           contexts[contextPos] = currentContext = new Context();
+           currentContext.declarePrefix("xml", XMLNS);
+       }
+    }
+
+    /**
+     * Returns true if namespace declaration attributes are placed into
+     * a namespace.  This behavior is not the default.
+     *
+     * @since SAX 2.1alpha
+     */
+    public boolean isNamespaceDeclUris ()
+       { return namespaceDeclUris; }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal state.
+    ////////////////////////////////////////////////////////////////////
+
+    private Context contexts[];
+    private Context currentContext;
+    private int contextPos;
+    private boolean namespaceDeclUris;
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal classes.
+    ////////////////////////////////////////////////////////////////////
+
+    /**
+     * Internal class for a single Namespace context.
+     *
+     * <p>This module caches and reuses Namespace contexts,
+     * so the number allocated
+     * will be equal to the element depth of the document, not to the total
+     * number of elements (i.e. 5-10 rather than tens of thousands).
+     * Also, data structures used to represent contexts are shared when
+     * possible (child contexts without declarations) to further reduce
+     * the amount of memory that's consumed.
+     * </p>
+     */
+    final class Context {
+
+       /**
+        * Create the root-level Namespace context.
+        */
+       Context ()
+       {
+           copyTables();
+       }
+       
+       
+       /**
+        * (Re)set the parent of this Namespace context.
+        * The context must either have been freshly constructed,
+        * or must have been cleared.
+        *
+        * @param context The parent Namespace context object.
+        */
+       void setParent (Context parent)
+       {
+           this.parent = parent;
+           declarations = null;
+           prefixTable = parent.prefixTable;
+           uriTable = parent.uriTable;
+           elementNameTable = parent.elementNameTable;
+           attributeNameTable = parent.attributeNameTable;
+           defaultNS = parent.defaultNS;
+           declSeen = false;
+           declsOK = true;
+       }
+
+       /**
+        * Makes associated state become collectible,
+        * invalidating this context.
+        * {@link #setParent} must be called before
+        * this context may be used again.
+        */
+       void clear ()
+       {
+           parent = null;
+           prefixTable = null;
+           uriTable = null;
+           elementNameTable = null;
+           attributeNameTable = null;
+           defaultNS = null;
+       }
+       
+       
+       /**
+        * Declare a Namespace prefix for this context.
+        *
+        * @param prefix The prefix to declare.
+        * @param uri The associated Namespace URI.
+        * @see org.xml.sax.helpers.NamespaceSupport#declarePrefix
+        */
+       void declarePrefix (String prefix, String uri)
+       {
+                               // Lazy processing...
+           if (!declsOK)
+               throw new IllegalStateException (
+                   "can't declare any more prefixes in this context");
+           if (!declSeen) {
+               copyTables();
+           }
+           if (declarations == null) {
+               declarations = new Vector();
+           }
+           
+           prefix = prefix.intern();
+           uri = uri.intern();
+           if ("".equals(prefix)) {
+               if ("".equals(uri)) {
+                   defaultNS = null;
+               } else {
+                   defaultNS = uri;
+               }
+           } else {
+               prefixTable.put(prefix, uri);
+               uriTable.put(uri, prefix); // may wipe out another prefix
+           }
+           declarations.addElement(prefix);
+       }
+
+
+       /**
+        * Process an XML qualified name in this context.
+        *
+        * @param qName The XML qualified name.
+        * @param isAttribute true if this is an attribute name.
+        * @return An array of three strings containing the
+        *         URI part (or empty string), the local part,
+        *         and the raw name, all internalized, or null
+        *         if there is an undeclared prefix.
+        * @see org.xml.sax.helpers.NamespaceSupport#processName
+        */
+       String [] processName (String qName, boolean isAttribute)
+       {
+           String name[];
+           Hashtable table;
+           
+                               // detect errors in call sequence
+           declsOK = false;
+
+                               // Select the appropriate table.
+           if (isAttribute) {
+               table = attributeNameTable;
+           } else {
+               table = elementNameTable;
+           }
+           
+                               // Start by looking in the cache, and
+                               // return immediately if the name
+                               // is already known in this content
+           name = (String[])table.get(qName);
+           if (name != null) {
+               return name;
+           }
+           
+                               // We haven't seen this name in this
+                               // context before.  Maybe in the parent
+                               // context, but we can't assume prefix
+                               // bindings are the same.
+           name = new String[3];
+           name[2] = qName.intern();
+           int index = qName.indexOf(':');
+           
+           
+                               // No prefix.
+           if (index == -1) {
+               if (isAttribute) {
+                   if (qName == "xmlns" && namespaceDeclUris)
+                       name[0] = NSDECL;
+                   else
+                       name[0] = "";
+               } else if (defaultNS == null) {
+                   name[0] = "";
+               } else {
+                   name[0] = defaultNS;
+               }
+               name[1] = name[2];
+           }
+           
+                               // Prefix
+           else {
+               String prefix = qName.substring(0, index);
+               String local = qName.substring(index+1);
+               String uri;
+               if ("".equals(prefix)) {
+                   uri = defaultNS;
+               } else {
+                   uri = (String)prefixTable.get(prefix);
+               }
+               if (uri == null
+                       || (!isAttribute && "xmlns".equals (prefix))) {
+                   return null;
+               }
+               name[0] = uri;
+               name[1] = local.intern();
+           }
+           
+                               // Save in the cache for future use.
+                               // (Could be shared with parent context...)
+           table.put(name[2], name);
+           return name;
+       }
+       
+
+       /**
+        * Look up the URI associated with a prefix in this context.
+        *
+        * @param prefix The prefix to look up.
+        * @return The associated Namespace URI, or null if none is
+        *         declared.    
+        * @see org.xml.sax.helpers.NamespaceSupport#getURI
+        */
+       String getURI (String prefix)
+       {
+           if ("".equals(prefix)) {
+               return defaultNS;
+           } else if (prefixTable == null) {
+               return null;
+           } else {
+               return (String)prefixTable.get(prefix);
+           }
+       }
+
+
+       /**
+        * Look up one of the prefixes associated with a URI in this context.
+        *
+        * <p>Since many prefixes may be mapped to the same URI,
+        * the return value may be unreliable.</p>
+        *
+        * @param uri The URI to look up.
+        * @return The associated prefix, or null if none is declared.
+        * @see org.xml.sax.helpers.NamespaceSupport#getPrefix
+        */
+       String getPrefix (String uri)
+       {
+           if (uriTable == null) {
+               return null;
+           } else {
+               return (String)uriTable.get(uri);
+           }
+       }
+       
+       
+       /**
+        * Return an enumeration of prefixes declared in this context.
+        *
+        * @return An enumeration of prefixes (possibly empty).
+        * @see org.xml.sax.helpers.NamespaceSupport#getDeclaredPrefixes
+        */
+       Enumeration getDeclaredPrefixes ()
+       {
+           if (declarations == null) {
+               return EMPTY_ENUMERATION;
+           } else {
+               return declarations.elements();
+           }
+       }
+       
+       
+       /**
+        * Return an enumeration of all prefixes currently in force.
+        *
+        * <p>The default prefix, if in force, is <em>not</em>
+        * returned, and will have to be checked for separately.</p>
+        *
+        * @return An enumeration of prefixes (never empty).
+        * @see org.xml.sax.helpers.NamespaceSupport#getPrefixes
+        */
+       Enumeration getPrefixes ()
+       {
+           if (prefixTable == null) {
+               return EMPTY_ENUMERATION;
+           } else {
+               return prefixTable.keys();
+           }
+       }
+       
+       
+\f
+       ////////////////////////////////////////////////////////////////
+       // Internal methods.
+       ////////////////////////////////////////////////////////////////
+
+
+       /**
+        * Copy on write for the internal tables in this context.
+        *
+        * <p>This class is optimized for the normal case where most
+        * elements do not contain Namespace declarations.</p>
+        */     
+       private void copyTables ()
+       {
+           if (prefixTable != null) {
+               prefixTable = (Hashtable)prefixTable.clone();
+           } else {
+               prefixTable = new Hashtable();
+           }
+           if (uriTable != null) {
+               uriTable = (Hashtable)uriTable.clone();
+           } else {
+               uriTable = new Hashtable();
+           }
+           elementNameTable = new Hashtable();
+           attributeNameTable = new Hashtable();
+           declSeen = true;
+       }
+
+
+\f
+       ////////////////////////////////////////////////////////////////
+       // Protected state.
+       ////////////////////////////////////////////////////////////////
+       
+       Hashtable prefixTable;
+       Hashtable uriTable;
+       Hashtable elementNameTable;
+       Hashtable attributeNameTable;
+       String defaultNS = null;
+       boolean declsOK = true;
+       
+
+\f
+       ////////////////////////////////////////////////////////////////
+       // Internal state.
+       ////////////////////////////////////////////////////////////////
+       
+       private Vector declarations = null;
+       private boolean declSeen = false;
+       private Context parent = null;
+    }
+}
+
+// end of NamespaceSupport.java
diff --git a/src/org/xml/sax/helpers/NewInstance.java b/src/org/xml/sax/helpers/NewInstance.java
new file mode 100644 (file)
index 0000000..6fc34d6
--- /dev/null
@@ -0,0 +1,79 @@
+// NewInstance.java - create a new instance of a class by name.
+// http://www.saxproject.org
+// Written by Edwin Goei, edwingo@apache.org
+// and by David Brownell, dbrownell@users.sourceforge.net
+// NO WARRANTY!  This class is in the Public Domain.
+// $Id: NewInstance.java,v 1.4 2002/01/30 20:52:27 dbrownell Exp $
+
+package org.xml.sax.helpers;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Create a new instance of a class by name.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class contains a static method for creating an instance of a
+ * class from an explicit class name.  It tries to use the thread's context
+ * ClassLoader if possible and falls back to using
+ * Class.forName(String).</p>
+ *
+ * <p>This code is designed to compile and run on JDK version 1.1 and later
+ * including versions of Java 2.</p>
+ *
+ * @author Edwin Goei, David Brownell
+ * @version 2.0.1 (sax2r2)
+ */
+class NewInstance {
+
+    /**
+     * Creates a new instance of the specified class name
+     *
+     * Package private so this code is not exposed at the API level.
+     */
+    static Object newInstance (ClassLoader classLoader, String className)
+        throws ClassNotFoundException, IllegalAccessException,
+            InstantiationException
+    {
+        Class driverClass;
+        if (classLoader == null) {
+            driverClass = Class.forName(className);
+        } else {
+            driverClass = classLoader.loadClass(className);
+        }
+        return driverClass.newInstance();
+    }
+
+    /**
+     * Figure out which ClassLoader to use.  For JDK 1.2 and later use
+     * the context ClassLoader.
+     */           
+    static ClassLoader getClassLoader ()
+    {
+        Method m = null;
+
+        try {
+            m = Thread.class.getMethod("getContextClassLoader", null);
+        } catch (NoSuchMethodException e) {
+            // Assume that we are running JDK 1.1, use the current ClassLoader
+            return NewInstance.class.getClassLoader();
+        }
+
+        try {
+            return (ClassLoader) m.invoke(Thread.currentThread(), null);
+        } catch (IllegalAccessException e) {
+            // assert(false)
+            throw new UnknownError(e.getMessage());
+        } catch (InvocationTargetException e) {
+            // assert(e.getTargetException() instanceof SecurityException)
+            throw new UnknownError(e.getMessage());
+        }
+    }
+}
diff --git a/src/org/xml/sax/helpers/ParserAdapter.java b/src/org/xml/sax/helpers/ParserAdapter.java
new file mode 100644 (file)
index 0000000..b723219
--- /dev/null
@@ -0,0 +1,1080 @@
+// ParserAdapter.java - adapt a SAX1 Parser to a SAX2 XMLReader.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the public domain.
+// $Id: ParserAdapter.java,v 1.16 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.helpers;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.xml.sax.Parser;     // deprecated
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.AttributeList; // deprecated
+import org.xml.sax.EntityResolver;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.DocumentHandler; // deprecated
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import org.xml.sax.XMLReader;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+
+
+/**
+ * Adapt a SAX1 Parser as a SAX2 XMLReader.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class wraps a SAX1 {@link org.xml.sax.Parser Parser}
+ * and makes it act as a SAX2 {@link org.xml.sax.XMLReader XMLReader},
+ * with feature, property, and Namespace support.  Note
+ * that it is not possible to report {@link org.xml.sax.ContentHandler#skippedEntity
+ * skippedEntity} events, since SAX1 does not make that information available.</p>
+ *
+ * <p>This adapter does not test for duplicate Namespace-qualified
+ * attribute names.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.helpers.XMLReaderAdapter
+ * @see org.xml.sax.XMLReader
+ * @see org.xml.sax.Parser
+ */
+public class ParserAdapter implements XMLReader, DocumentHandler
+{
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Constructors.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Construct a new parser adapter.
+     *
+     * <p>Use the "org.xml.sax.parser" property to locate the
+     * embedded SAX1 driver.</p>
+     *
+     * @exception SAXException If the embedded driver
+     *            cannot be instantiated or if the
+     *            org.xml.sax.parser property is not specified.
+     */
+    public ParserAdapter ()
+      throws SAXException
+    {
+       super();
+
+       String driver = System.getProperty("org.xml.sax.parser");
+
+       try {
+           setup(ParserFactory.makeParser());
+       } catch (ClassNotFoundException e1) {
+           throw new
+               SAXException("Cannot find SAX1 driver class " +
+                            driver, e1);
+       } catch (IllegalAccessException e2) {
+           throw new
+               SAXException("SAX1 driver class " +
+                            driver +
+                            " found but cannot be loaded", e2);
+       } catch (InstantiationException e3) {
+           throw new
+               SAXException("SAX1 driver class " +
+                            driver +
+                            " loaded but cannot be instantiated", e3);
+       } catch (ClassCastException e4) {
+           throw new
+               SAXException("SAX1 driver class " +
+                            driver +
+                            " does not implement org.xml.sax.Parser");
+       } catch (NullPointerException e5) {
+           throw new 
+               SAXException("System property org.xml.sax.parser not specified");
+       }
+    }
+
+
+    /**
+     * Construct a new parser adapter.
+     *
+     * <p>Note that the embedded parser cannot be changed once the
+     * adapter is created; to embed a different parser, allocate
+     * a new ParserAdapter.</p>
+     *
+     * @param parser The SAX1 parser to embed.
+     * @exception java.lang.NullPointerException If the parser parameter
+     *            is null.
+     */
+    public ParserAdapter (Parser parser)
+    {
+       super();
+       setup(parser);
+    }
+
+
+    /**
+     * Internal setup method.
+     *
+     * @param parser The embedded parser.
+     * @exception java.lang.NullPointerException If the parser parameter
+     *            is null.
+     */
+    private void setup (Parser parser)
+    {
+       if (parser == null) {
+           throw new
+               NullPointerException("Parser argument must not be null");
+       }
+       this.parser = parser;
+       atts = new AttributesImpl();
+       nsSupport = new NamespaceSupport();
+       attAdapter = new AttributeListAdapter();
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.XMLReader.
+    ////////////////////////////////////////////////////////////////////
+
+
+    //
+    // Internal constants for the sake of convenience.
+    //
+    private final static String FEATURES = "http://xml.org/sax/features/";
+    private final static String NAMESPACES = FEATURES + "namespaces";
+    private final static String NAMESPACE_PREFIXES = FEATURES + "namespace-prefixes";
+    private final static String XMLNS_URIs = FEATURES + "xmlns-uris";
+
+
+    /**
+     * Set a feature flag for the parser.
+     *
+     * <p>The only features recognized are namespaces and 
+     * namespace-prefixes.</p>
+     *
+     * @param name The feature name, as a complete URI.
+     * @param value The requested feature value.
+     * @exception SAXNotRecognizedException If the feature
+     *            can't be assigned or retrieved.
+     * @exception SAXNotSupportedException If the feature
+     *            can't be assigned that value.
+     * @see org.xml.sax.XMLReader#setFeature
+     */
+    @Override
+               public void setFeature (String name, boolean value)
+       throws SAXNotRecognizedException, SAXNotSupportedException
+    {
+       if (name.equals(NAMESPACES)) {
+           checkNotParsing("feature", name);
+           namespaces = value;
+           if (!namespaces && !prefixes) {
+               prefixes = true;
+           }
+       } else if (name.equals(NAMESPACE_PREFIXES)) {
+           checkNotParsing("feature", name);
+           prefixes = value;
+           if (!prefixes && !namespaces) {
+               namespaces = true;
+           }
+       } else if (name.equals(XMLNS_URIs)) {
+           checkNotParsing("feature", name);
+           uris = value;
+       } else {
+           throw new SAXNotRecognizedException("Feature: " + name);
+       }
+    }
+
+
+    /**
+     * Check a parser feature flag.
+     *
+     * <p>The only features recognized are namespaces and 
+     * namespace-prefixes.</p>
+     *
+     * @param name The feature name, as a complete URI.
+     * @return The current feature value.
+     * @exception SAXNotRecognizedException If the feature
+     *            value can't be assigned or retrieved.
+     * @exception SAXNotSupportedException If the
+     *            feature is not currently readable.
+     * @see org.xml.sax.XMLReader#setFeature
+     */
+    @Override
+               public boolean getFeature (String name)
+       throws SAXNotRecognizedException, SAXNotSupportedException
+    {
+       if (name.equals(NAMESPACES)) {
+           return namespaces;
+       } else if (name.equals(NAMESPACE_PREFIXES)) {
+           return prefixes;
+       } else if (name.equals(XMLNS_URIs)) {
+           return uris;
+       } else {
+           throw new SAXNotRecognizedException("Feature: " + name);
+       }
+    }
+
+
+    /**
+     * Set a parser property.
+     *
+     * <p>No properties are currently recognized.</p>
+     *
+     * @param name The property name.
+     * @param value The property value.
+     * @exception SAXNotRecognizedException If the property
+     *            value can't be assigned or retrieved.
+     * @exception SAXNotSupportedException If the property
+     *            can't be assigned that value.
+     * @see org.xml.sax.XMLReader#setProperty
+     */
+    @Override
+               public void setProperty (String name, Object value)
+       throws SAXNotRecognizedException, SAXNotSupportedException
+    {
+       throw new SAXNotRecognizedException("Property: " + name);
+    }
+
+
+    /**
+     * Get a parser property.
+     *
+     * <p>No properties are currently recognized.</p>
+     *
+     * @param name The property name.
+     * @return The property value.
+     * @exception SAXNotRecognizedException If the property
+     *            value can't be assigned or retrieved.
+     * @exception SAXNotSupportedException If the property
+     *            value is not currently readable.
+     * @see org.xml.sax.XMLReader#getProperty
+     */
+    @Override
+               public Object getProperty (String name)
+       throws SAXNotRecognizedException, SAXNotSupportedException
+    {
+       throw new SAXNotRecognizedException("Property: " + name);
+    }
+
+
+    /**
+     * Set the entity resolver.
+     *
+     * @param resolver The new entity resolver.
+     * @see org.xml.sax.XMLReader#setEntityResolver
+     */
+    @Override
+               public void setEntityResolver (EntityResolver resolver)
+    {
+       entityResolver = resolver;
+    }
+
+
+    /**
+     * Return the current entity resolver.
+     *
+     * @return The current entity resolver, or null if none was supplied.
+     * @see org.xml.sax.XMLReader#getEntityResolver
+     */
+    @Override
+               public EntityResolver getEntityResolver ()
+    {
+       return entityResolver;
+    }
+
+
+    /**
+     * Set the DTD handler.
+     *
+     * @param handler the new DTD handler
+     * @see org.xml.sax.XMLReader#setEntityResolver
+     */
+    @Override
+               public void setDTDHandler (DTDHandler handler)
+    {
+       dtdHandler = handler;
+    }
+
+
+    /**
+     * Return the current DTD handler.
+     *
+     * @return the current DTD handler, or null if none was supplied
+     * @see org.xml.sax.XMLReader#getEntityResolver
+     */
+    @Override
+               public DTDHandler getDTDHandler ()
+    {
+       return dtdHandler;
+    }
+
+
+    /**
+     * Set the content handler.
+     *
+     * @param handler the new content handler
+     * @see org.xml.sax.XMLReader#setEntityResolver
+     */
+    @Override
+               public void setContentHandler (ContentHandler handler)
+    {
+       contentHandler = handler;
+    }
+
+
+    /**
+     * Return the current content handler.
+     *
+     * @return The current content handler, or null if none was supplied.
+     * @see org.xml.sax.XMLReader#getEntityResolver
+     */
+    @Override
+               public ContentHandler getContentHandler ()
+    {
+       return contentHandler;
+    }
+
+
+    /**
+     * Set the error handler.
+     *
+     * @param handler The new error handler.
+     * @see org.xml.sax.XMLReader#setEntityResolver
+     */
+    @Override
+               public void setErrorHandler (ErrorHandler handler)
+    {
+       errorHandler = handler;
+    }
+
+
+    /**
+     * Return the current error handler.
+     *
+     * @return The current error handler, or null if none was supplied.
+     * @see org.xml.sax.XMLReader#getEntityResolver
+     */
+    @Override
+               public ErrorHandler getErrorHandler ()
+    {
+       return errorHandler;
+    }
+
+
+    /**
+     * Parse an XML document.
+     *
+     * @param systemId The absolute URL of the document.
+     * @exception java.io.IOException If there is a problem reading
+     *            the raw content of the document.
+     * @exception SAXException If there is a problem
+     *            processing the document.
+     * @see #parse(org.xml.sax.InputSource)
+     * @see org.xml.sax.Parser#parse(java.lang.String)
+     */
+    @Override
+               public void parse (String systemId)
+       throws IOException, SAXException
+    {
+       parse(new InputSource(systemId));
+    }
+
+
+    /**
+     * Parse an XML document.
+     *
+     * @param input An input source for the document.
+     * @exception java.io.IOException If there is a problem reading
+     *            the raw content of the document.
+     * @exception SAXException If there is a problem
+     *            processing the document.
+     * @see #parse(java.lang.String)
+     * @see org.xml.sax.Parser#parse(org.xml.sax.InputSource)
+     */
+    @Override
+               public void parse (InputSource input)
+       throws IOException, SAXException
+    {
+       if (parsing) {
+           throw new SAXException("Parser is already in use");
+       }
+       setupParser();
+       parsing = true;
+       try {
+           parser.parse(input);
+       } finally {
+           parsing = false;
+       }
+       parsing = false;
+    }
+
+
+
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.DocumentHandler.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Adapter implementation method; do not call.
+     * Adapt a SAX1 document locator event.
+     *
+     * @param locator A document locator.
+     * @see org.xml.sax.ContentHandler#setDocumentLocator
+     */
+    @Override
+               public void setDocumentLocator (Locator locator)
+    {
+       this.locator = locator;
+       if (contentHandler != null) {
+           contentHandler.setDocumentLocator(locator);
+       }
+    }
+
+
+    /**
+     * Adapter implementation method; do not call.
+     * Adapt a SAX1 start document event.
+     *
+     * @exception SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.DocumentHandler#startDocument
+     */
+    @Override
+               public void startDocument ()
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.startDocument();
+       }
+    }
+
+
+    /**
+     * Adapter implementation method; do not call.
+     * Adapt a SAX1 end document event.
+     *
+     * @exception SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.DocumentHandler#endDocument
+     */
+    @Override
+               public void endDocument ()
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.endDocument();
+       }
+    }
+
+
+    /**
+     * Adapter implementation method; do not call.
+     * Adapt a SAX1 startElement event.
+     *
+     * <p>If necessary, perform Namespace processing.</p>
+     *
+     * @param qName The qualified (prefixed) name.
+     * @param qAtts The XML attribute list (with qnames).
+     * @exception SAXException The client may raise a
+     *            processing exception.
+     */
+    @Override
+               public void startElement (String qName, AttributeList qAtts)
+       throws SAXException
+    {
+                               // These are exceptions from the
+                               // first pass; they should be
+                               // ignored if there's a second pass,
+                               // but reported otherwise.
+       Vector exceptions = null;
+
+                               // If we're not doing Namespace
+                               // processing, dispatch this quickly.
+       if (!namespaces) {
+           if (contentHandler != null) {
+               attAdapter.setAttributeList(qAtts);
+               contentHandler.startElement("", "", qName.intern(),
+                                           attAdapter);
+           }
+           return;
+       }
+
+
+                               // OK, we're doing Namespace processing.
+       nsSupport.pushContext();
+       int length = qAtts.getLength();
+       
+                               // First pass:  handle NS decls
+       for (int i = 0; i < length; i++) {
+           String attQName = qAtts.getName(i);
+
+           if (!attQName.startsWith("xmlns"))
+               continue;
+                               // Could be a declaration...
+           String prefix;
+           int n = attQName.indexOf(':');
+
+                               // xmlns=...
+           if (n == -1 && attQName.length () == 5) {
+               prefix = "";
+           } else if (n != 5) {
+               // XML namespaces spec doesn't discuss "xmlnsf:oo"
+               // (and similarly named) attributes ... at most, warn
+               continue;
+           } else              // xmlns:foo=...
+               prefix = attQName.substring(n+1);
+
+           String value = qAtts.getValue(i);
+           if (!nsSupport.declarePrefix(prefix, value)) {
+               reportError("Illegal Namespace prefix: " + prefix);
+               continue;
+           }
+           if (contentHandler != null)
+               contentHandler.startPrefixMapping(prefix, value);
+       }
+       
+                               // Second pass: copy all relevant
+                               // attributes into the SAX2 AttributeList
+                               // using updated prefix bindings
+       atts.clear();
+       for (int i = 0; i < length; i++) {
+           String attQName = qAtts.getName(i);
+           String type = qAtts.getType(i);
+           String value = qAtts.getValue(i);
+
+                               // Declaration?
+           if (attQName.startsWith("xmlns")) {
+               String prefix;
+               int n = attQName.indexOf(':');
+
+               if (n == -1 && attQName.length () == 5) {
+                   prefix = "";
+               } else if (n != 5) {
+                   // XML namespaces spec doesn't discuss "xmlnsf:oo"
+                   // (and similarly named) attributes ... ignore
+                   prefix = null;
+               } else {
+                   prefix = attQName.substring(6);
+               }
+                               // Yes, decl:  report or prune
+               if (prefix != null) {
+                   if (prefixes) {
+                       if (uris)
+                           // note funky case:  localname can be null
+                           // when declaring the default prefix, and
+                           // yet the uri isn't null.
+                           atts.addAttribute (nsSupport.XMLNS, prefix,
+                                   attQName.intern(), type, value);
+                       else
+                           atts.addAttribute ("", "",
+                                   attQName.intern(), type, value);
+                   }
+                   continue;
+               }
+           } 
+
+                               // Not a declaration -- report
+           try {
+               String attName[] = processName(attQName, true, true);
+               atts.addAttribute(attName[0], attName[1], attName[2],
+                                 type, value);
+           } catch (SAXException e) {
+               if (exceptions == null)
+                   exceptions = new Vector();
+               exceptions.addElement(e);
+               atts.addAttribute("", attQName, attQName, type, value);
+           }
+       }
+       
+       // now handle the deferred exception reports
+       if (exceptions != null && errorHandler != null) {
+           for (int i = 0; i < exceptions.size(); i++)
+               errorHandler.error((SAXParseException)
+                               (exceptions.elementAt(i)));
+       }
+
+                               // OK, finally report the event.
+       if (contentHandler != null) {
+           String name[] = processName(qName, false, false);
+           contentHandler.startElement(name[0], name[1], name[2], atts);
+       }
+    }
+
+
+    /**
+     * Adapter implementation method; do not call.
+     * Adapt a SAX1 end element event.
+     *
+     * @param qName The qualified (prefixed) name.
+     * @exception SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.DocumentHandler#endElement
+     */
+    @Override
+               public void endElement (String qName)
+       throws SAXException
+    {
+                               // If we're not doing Namespace
+                               // processing, dispatch this quickly.
+       if (!namespaces) {
+           if (contentHandler != null) {
+               contentHandler.endElement("", "", qName.intern());
+           }
+           return;
+       }
+
+                               // Split the name.
+       String names[] = processName(qName, false, false);
+       if (contentHandler != null) {
+           contentHandler.endElement(names[0], names[1], names[2]);
+           Enumeration prefixes = nsSupport.getDeclaredPrefixes();
+           while (prefixes.hasMoreElements()) {
+               String prefix = (String)prefixes.nextElement();
+               contentHandler.endPrefixMapping(prefix);
+           }
+       }
+       nsSupport.popContext();
+    }
+
+
+    /**
+     * Adapter implementation method; do not call.
+     * Adapt a SAX1 characters event.
+     *
+     * @param ch An array of characters.
+     * @param start The starting position in the array.
+     * @param length The number of characters to use.
+     * @exception SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.DocumentHandler#characters
+     */
+    @Override
+               public void characters (char ch[], int start, int length)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.characters(ch, start, length);
+       }
+    }
+
+
+    /**
+     * Adapter implementation method; do not call.
+     * Adapt a SAX1 ignorable whitespace event.
+     *
+     * @param ch An array of characters.
+     * @param start The starting position in the array.
+     * @param length The number of characters to use.
+     * @exception SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.DocumentHandler#ignorableWhitespace
+     */
+    @Override
+               public void ignorableWhitespace (char ch[], int start, int length)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.ignorableWhitespace(ch, start, length);
+       }
+    }
+
+
+    /**
+     * Adapter implementation method; do not call.
+     * Adapt a SAX1 processing instruction event.
+     *
+     * @param target The processing instruction target.
+     * @param data The remainder of the processing instruction
+     * @exception SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.DocumentHandler#processingInstruction
+     */
+    @Override
+               public void processingInstruction (String target, String data)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.processingInstruction(target, data);
+       }
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal utility methods.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Initialize the parser before each run.
+     */
+    private void setupParser ()
+    {
+       // catch an illegal "nonsense" state.
+       if (!prefixes && !namespaces)
+           throw new IllegalStateException ();
+
+       nsSupport.reset();
+       if (uris)
+           nsSupport.setNamespaceDeclUris (true);
+
+       if (entityResolver != null) {
+           parser.setEntityResolver(entityResolver);
+       }
+       if (dtdHandler != null) {
+           parser.setDTDHandler(dtdHandler);
+       }
+       if (errorHandler != null) {
+           parser.setErrorHandler(errorHandler);
+       }
+       parser.setDocumentHandler(this);
+       locator = null;
+    }
+
+
+    /**
+     * Process a qualified (prefixed) name.
+     *
+     * <p>If the name has an undeclared prefix, use only the qname
+     * and make an ErrorHandler.error callback in case the app is
+     * interested.</p>
+     *
+     * @param qName The qualified (prefixed) name.
+     * @param isAttribute true if this is an attribute name.
+     * @return The name split into three parts.
+     * @exception SAXException The client may throw
+     *            an exception if there is an error callback.
+     */
+    private String [] processName (String qName, boolean isAttribute,
+                                  boolean useException)
+       throws SAXException
+    {
+       String parts[] = nsSupport.processName(qName, nameParts,
+                                              isAttribute);
+       if (parts == null) {
+           if (useException)
+               throw makeException("Undeclared prefix: " + qName);
+           reportError("Undeclared prefix: " + qName);
+           parts = new String[3];
+           parts[0] = parts[1] = "";
+           parts[2] = qName.intern();
+       }
+       return parts;
+    }
+
+
+    /**
+     * Report a non-fatal error.
+     *
+     * @param message The error message.
+     * @exception SAXException The client may throw
+     *            an exception.
+     */
+    void reportError (String message)
+       throws SAXException
+    {
+       if (errorHandler != null)
+           errorHandler.error(makeException(message));
+    }
+
+    
+    /**
+     * Construct an exception for the current context.
+     *
+     * @param message The error message.
+     */
+    private SAXParseException makeException (String message)
+    {
+       if (locator != null) {
+           return new SAXParseException(message, locator);
+       } else {
+           return new SAXParseException(message, null, null, -1, -1);
+       }
+    }
+
+
+    /**
+     * Throw an exception if we are parsing.
+     *
+     * <p>Use this method to detect illegal feature or
+     * property changes.</p>
+     *
+     * @param type The type of thing (feature or property).
+     * @param name The feature or property name.
+     * @exception SAXNotSupportedException If a
+     *            document is currently being parsed.
+     */
+    private void checkNotParsing (String type, String name)
+       throws SAXNotSupportedException
+    {
+       if (parsing) {
+           throw new SAXNotSupportedException("Cannot change " +
+                                              type + ' ' +
+                                              name + " while parsing");
+                                              
+       }
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal state.
+    ////////////////////////////////////////////////////////////////////
+
+    private NamespaceSupport nsSupport;
+    private AttributeListAdapter attAdapter;
+
+    private boolean parsing = false;
+    private String nameParts[] = new String[3];
+
+    private Parser parser = null;
+
+    private AttributesImpl atts = null;
+
+                               // Features
+    private boolean namespaces = true;
+    private boolean prefixes = false;
+    private boolean uris = false;
+
+                               // Properties
+
+                               // Handlers
+    Locator locator;
+
+    EntityResolver entityResolver = null;
+    DTDHandler dtdHandler = null;
+    ContentHandler contentHandler = null;
+    ErrorHandler errorHandler = null;
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Inner class to wrap an AttributeList when not doing NS proc.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Adapt a SAX1 AttributeList as a SAX2 Attributes object.
+     *
+     * <p>This class is in the Public Domain, and comes with NO
+     * WARRANTY of any kind.</p>
+     *
+     * <p>This wrapper class is used only when Namespace support
+     * is disabled -- it provides pretty much a direct mapping
+     * from SAX1 to SAX2, except that names and types are 
+     * interned whenever requested.</p>
+     */
+    final class AttributeListAdapter implements Attributes
+    {
+
+       /**
+        * Construct a new adapter.
+        */
+       AttributeListAdapter ()
+       {
+       }
+
+
+       /**
+        * Set the embedded AttributeList.
+        *
+        * <p>This method must be invoked before any of the others
+        * can be used.</p>
+        *
+        * @param The SAX1 attribute list (with qnames).
+        */
+       void setAttributeList (AttributeList qAtts)
+       {
+           this.qAtts = qAtts;
+       }
+
+
+       /**
+        * Return the length of the attribute list.
+        *
+        * @return The number of attributes in the list.
+        * @see org.xml.sax.Attributes#getLength
+        */
+       @Override
+       public int getLength ()
+       {
+           return qAtts.getLength();
+       }
+
+
+       /**
+        * Return the Namespace URI of the specified attribute.
+        *
+        * @param The attribute's index.
+        * @return Always the empty string.
+        * @see org.xml.sax.Attributes#getURI
+        */
+       @Override
+       public String getURI (int i)
+       {
+           return "";
+       }
+
+
+       /**
+        * Return the local name of the specified attribute.
+        *
+        * @param The attribute's index.
+        * @return Always the empty string.
+        * @see org.xml.sax.Attributes#getLocalName
+        */
+       @Override
+       public String getLocalName (int i)
+       {
+           return "";
+       }
+
+
+       /**
+        * Return the qualified (prefixed) name of the specified attribute.
+        *
+        * @param The attribute's index.
+        * @return The attribute's qualified name, internalized.
+        */
+       @Override
+       public String getQName (int i)
+       {
+           return qAtts.getName(i).intern();
+       }
+
+
+       /**
+        * Return the type of the specified attribute.
+        *
+        * @param The attribute's index.
+        * @return The attribute's type as an internalized string.
+        */
+       @Override
+       public String getType (int i)
+       {
+           return qAtts.getType(i).intern();
+       }
+
+
+       /**
+        * Return the value of the specified attribute.
+        *
+        * @param The attribute's index.
+        * @return The attribute's value.
+        */
+       @Override
+       public String getValue (int i)
+       {
+           return qAtts.getValue(i);
+       }
+
+
+       /**
+        * Look up an attribute index by Namespace name.
+        *
+        * @param uri The Namespace URI or the empty string.
+        * @param localName The local name.
+        * @return The attributes index, or -1 if none was found.
+        * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
+        */
+       @Override
+       public int getIndex (String uri, String localName)
+       {
+           return -1;
+       }
+
+
+       /**
+        * Look up an attribute index by qualified (prefixed) name.
+        *
+        * @param qName The qualified name.
+        * @return The attributes index, or -1 if none was found.
+        * @see org.xml.sax.Attributes#getIndex(java.lang.String)
+        */
+       @Override
+       public int getIndex (String qName)
+       {
+           int max = atts.getLength();
+           for (int i = 0; i < max; i++) {
+               if (qAtts.getName(i).equals(qName)) {
+                   return i;
+               }
+           }
+           return -1;
+       }
+
+
+       /**
+        * Look up the type of an attribute by Namespace name.
+        *
+        * @param uri The Namespace URI
+        * @param localName The local name.
+        * @return The attribute's type as an internalized string.
+        */
+       @Override
+       public String getType (String uri, String localName)
+       {
+           return null;
+       }
+
+
+       /**
+        * Look up the type of an attribute by qualified (prefixed) name.
+        *
+        * @param qName The qualified name.
+        * @return The attribute's type as an internalized string.
+        */
+       @Override
+       public String getType (String qName)
+       {
+           return qAtts.getType(qName).intern();
+       }
+
+
+       /**
+        * Look up the value of an attribute by Namespace name.
+        *
+        * @param uri The Namespace URI
+        * @param localName The local name.
+        * @return The attribute's value.
+        */
+       @Override
+       public String getValue (String uri, String localName)
+       {
+           return null;
+       }
+
+
+       /**
+        * Look up the value of an attribute by qualified (prefixed) name.
+        *
+        * @param qName The qualified name.
+        * @return The attribute's value.
+        */
+       @Override
+       public String getValue (String qName)
+       {
+           return qAtts.getValue(qName);
+       }
+
+       private AttributeList qAtts;
+    }
+}
+
+// end of ParserAdapter.java
diff --git a/src/org/xml/sax/helpers/ParserFactory.java b/src/org/xml/sax/helpers/ParserFactory.java
new file mode 100644 (file)
index 0000000..8ca7838
--- /dev/null
@@ -0,0 +1,123 @@
+// SAX parser factory.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: ParserFactory.java,v 1.7 2002/01/30 20:52:36 dbrownell Exp $
+
+package org.xml.sax.helpers;
+
+import org.xml.sax.Parser;
+
+
+/**
+ * Java-specific class for dynamically loading SAX parsers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p><strong>Note:</strong> This class is designed to work with the now-deprecated
+ * SAX1 {@link org.xml.sax.Parser Parser} class.  SAX2 applications should use
+ * {@link org.xml.sax.helpers.XMLReaderFactory XMLReaderFactory} instead.</p>
+ *
+ * <p>ParserFactory is not part of the platform-independent definition
+ * of SAX; it is an additional convenience class designed
+ * specifically for Java XML application writers.  SAX applications
+ * can use the static methods in this class to allocate a SAX parser
+ * dynamically at run-time based either on the value of the
+ * `org.xml.sax.parser' system property or on a string containing the class
+ * name.</p>
+ *
+ * <p>Note that the application still requires an XML parser that
+ * implements SAX1.</p>
+ *
+ * @deprecated This class works with the deprecated
+ *             {@link org.xml.sax.Parser Parser}
+ *             interface.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ */
+public class ParserFactory {
+    
+    
+    /**
+     * Private null constructor.
+     */
+    private ParserFactory ()
+    {
+    }
+    
+    
+    /**
+     * Create a new SAX parser using the `org.xml.sax.parser' system property.
+     *
+     * <p>The named class must exist and must implement the
+     * {@link org.xml.sax.Parser Parser} interface.</p>
+     *
+     * @exception java.lang.NullPointerException There is no value
+     *            for the `org.xml.sax.parser' system property.
+     * @exception java.lang.ClassNotFoundException The SAX parser
+     *            class was not found (check your CLASSPATH).
+     * @exception IllegalAccessException The SAX parser class was
+     *            found, but you do not have permission to load
+     *            it.
+     * @exception InstantiationException The SAX parser class was
+     *            found but could not be instantiated.
+     * @exception java.lang.ClassCastException The SAX parser class
+     *            was found and instantiated, but does not implement
+     *            org.xml.sax.Parser.
+     * @see #makeParser(java.lang.String)
+     * @see org.xml.sax.Parser
+     */
+    public static Parser makeParser ()
+       throws ClassNotFoundException,
+       IllegalAccessException, 
+       InstantiationException,
+       NullPointerException,
+       ClassCastException
+    {
+       String className = System.getProperty("org.xml.sax.parser");
+       if (className == null) {
+           throw new NullPointerException("No value for sax.parser property");
+       } else {
+           return makeParser(className);
+       }
+    }
+    
+    
+    /**
+     * Create a new SAX parser object using the class name provided.
+     *
+     * <p>The named class must exist and must implement the
+     * {@link org.xml.sax.Parser Parser} interface.</p>
+     *
+     * @param className A string containing the name of the
+     *                  SAX parser class.
+     * @exception java.lang.ClassNotFoundException The SAX parser
+     *            class was not found (check your CLASSPATH).
+     * @exception IllegalAccessException The SAX parser class was
+     *            found, but you do not have permission to load
+     *            it.
+     * @exception InstantiationException The SAX parser class was
+     *            found but could not be instantiated.
+     * @exception java.lang.ClassCastException The SAX parser class
+     *            was found and instantiated, but does not implement
+     *            org.xml.sax.Parser.
+     * @see #makeParser()
+     * @see org.xml.sax.Parser
+     */
+    public static Parser makeParser (String className)
+       throws ClassNotFoundException,
+       IllegalAccessException, 
+       InstantiationException,
+       ClassCastException
+    {
+       return (Parser) NewInstance.newInstance (
+               NewInstance.getClassLoader (), className);
+    }
+    
+}
+
diff --git a/src/org/xml/sax/helpers/XMLFilterImpl.java b/src/org/xml/sax/helpers/XMLFilterImpl.java
new file mode 100644 (file)
index 0000000..6f329d9
--- /dev/null
@@ -0,0 +1,746 @@
+// XMLFilterImpl.java - base SAX2 filter implementation.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the Public Domain.
+// $Id: XMLFilterImpl.java,v 1.9 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.helpers;
+
+import java.io.IOException;
+
+import org.xml.sax.XMLReader;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.Attributes;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.SAXNotRecognizedException;
+
+
+/**
+ * Base class for deriving an XML filter.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class is designed to sit between an {@link org.xml.sax.XMLReader
+ * XMLReader} and the client application's event handlers.  By default, it
+ * does nothing but pass requests up to the reader and events
+ * on to the handlers unmodified, but subclasses can override
+ * specific methods to modify the event stream or the configuration
+ * requests as they pass through.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.XMLFilter
+ * @see org.xml.sax.XMLReader
+ * @see org.xml.sax.EntityResolver
+ * @see org.xml.sax.DTDHandler
+ * @see org.xml.sax.ContentHandler
+ * @see org.xml.sax.ErrorHandler
+ */
+public class XMLFilterImpl
+    implements XMLFilter, EntityResolver, DTDHandler, ContentHandler, ErrorHandler
+{
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Constructors.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Construct an empty XML filter, with no parent.
+     *
+     * <p>This filter will have no parent: you must assign a parent
+     * before you start a parse or do any configuration with
+     * setFeature or setProperty, unless you use this as a pure event
+     * consumer rather than as an {@link XMLReader}.</p>
+     *
+     * @see org.xml.sax.XMLReader#setFeature
+     * @see org.xml.sax.XMLReader#setProperty
+     * @see #setParent
+     */
+    public XMLFilterImpl ()
+    {
+       super();
+    }
+
+
+    /**
+     * Construct an XML filter with the specified parent.
+     *
+     * @see #setParent
+     * @see #getParent
+     */
+    public XMLFilterImpl (XMLReader parent)
+    {
+        super();
+       setParent(parent);
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.XMLFilter.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Set the parent reader.
+     *
+     * <p>This is the {@link org.xml.sax.XMLReader XMLReader} from which 
+     * this filter will obtain its events and to which it will pass its 
+     * configuration requests.  The parent may itself be another filter.</p>
+     *
+     * <p>If there is no parent reader set, any attempt to parse
+     * or to set or get a feature or property will fail.</p>
+     *
+     * @param parent The parent XML reader.
+     * @see #getParent
+     */
+    @Override
+               public void setParent (XMLReader parent)
+    {
+       this.parent = parent;
+    }
+
+
+    /**
+     * Get the parent reader.
+     *
+     * @return The parent XML reader, or null if none is set.
+     * @see #setParent
+     */
+    @Override
+               public XMLReader getParent ()
+    {
+       return parent;
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.XMLReader.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Set the value of a feature.
+     *
+     * <p>This will always fail if the parent is null.</p>
+     *
+     * @param name The feature name.
+     * @param value The requested feature value.
+     * @exception org.xml.sax.SAXNotRecognizedException If the feature
+     *            value can't be assigned or retrieved from the parent.
+     * @exception org.xml.sax.SAXNotSupportedException When the
+     *            parent recognizes the feature name but 
+     *            cannot set the requested value.
+     */
+    @Override
+               public void setFeature (String name, boolean value)
+       throws SAXNotRecognizedException, SAXNotSupportedException
+    {
+       if (parent != null) {
+           parent.setFeature(name, value);
+       } else {
+           throw new SAXNotRecognizedException("Feature: " + name);
+       }
+    }
+
+
+    /**
+     * Look up the value of a feature.
+     *
+     * <p>This will always fail if the parent is null.</p>
+     *
+     * @param name The feature name.
+     * @return The current value of the feature.
+     * @exception org.xml.sax.SAXNotRecognizedException If the feature
+     *            value can't be assigned or retrieved from the parent.
+     * @exception org.xml.sax.SAXNotSupportedException When the
+     *            parent recognizes the feature name but 
+     *            cannot determine its value at this time.
+     */
+    @Override
+               public boolean getFeature (String name)
+       throws SAXNotRecognizedException, SAXNotSupportedException
+    {
+       if (parent != null) {
+           return parent.getFeature(name);
+       } else {
+           throw new SAXNotRecognizedException("Feature: " + name);
+       }
+    }
+
+
+    /**
+     * Set the value of a property.
+     *
+     * <p>This will always fail if the parent is null.</p>
+     *
+     * @param name The property name.
+     * @param value The requested property value.
+     * @exception org.xml.sax.SAXNotRecognizedException If the property
+     *            value can't be assigned or retrieved from the parent.
+     * @exception org.xml.sax.SAXNotSupportedException When the
+     *            parent recognizes the property name but 
+     *            cannot set the requested value.
+     */
+    @Override
+               public void setProperty (String name, Object value)
+       throws SAXNotRecognizedException, SAXNotSupportedException
+    {
+       if (parent != null) {
+           parent.setProperty(name, value);
+       } else {
+           throw new SAXNotRecognizedException("Property: " + name);
+       }
+    }
+
+
+    /**
+     * Look up the value of a property.
+     *
+     * @param name The property name.
+     * @return The current value of the property.
+     * @exception org.xml.sax.SAXNotRecognizedException If the property
+     *            value can't be assigned or retrieved from the parent.
+     * @exception org.xml.sax.SAXNotSupportedException When the
+     *            parent recognizes the property name but 
+     *            cannot determine its value at this time.
+     */
+    @Override
+               public Object getProperty (String name)
+       throws SAXNotRecognizedException, SAXNotSupportedException
+    {
+       if (parent != null) {
+           return parent.getProperty(name);
+       } else {
+           throw new SAXNotRecognizedException("Property: " + name);
+       }
+    }
+
+
+    /**
+     * Set the entity resolver.
+     *
+     * @param resolver The new entity resolver.
+     */
+    @Override
+               public void setEntityResolver (EntityResolver resolver)
+    {
+       entityResolver = resolver;
+    }
+
+
+    /**
+     * Get the current entity resolver.
+     *
+     * @return The current entity resolver, or null if none was set.
+     */
+    @Override
+               public EntityResolver getEntityResolver ()
+    {
+       return entityResolver;
+    }
+
+
+    /**
+     * Set the DTD event handler.
+     *
+     * @param handler the new DTD handler
+     */
+    @Override
+               public void setDTDHandler (DTDHandler handler)
+    {
+       dtdHandler = handler;
+    }
+
+
+    /**
+     * Get the current DTD event handler.
+     *
+     * @return The current DTD handler, or null if none was set.
+     */
+    @Override
+               public DTDHandler getDTDHandler ()
+    {
+       return dtdHandler;
+    }
+
+
+    /**
+     * Set the content event handler.
+     *
+     * @param handler the new content handler
+     */
+    @Override
+               public void setContentHandler (ContentHandler handler)
+    {
+       contentHandler = handler;
+    }
+
+
+    /**
+     * Get the content event handler.
+     *
+     * @return The current content handler, or null if none was set.
+     */
+    @Override
+               public ContentHandler getContentHandler ()
+    {
+       return contentHandler;
+    }
+
+
+    /**
+     * Set the error event handler.
+     *
+     * @param handler the new error handler
+     */
+    @Override
+               public void setErrorHandler (ErrorHandler handler)
+    {
+       errorHandler = handler;
+    }
+
+
+    /**
+     * Get the current error event handler.
+     *
+     * @return The current error handler, or null if none was set.
+     */
+    @Override
+               public ErrorHandler getErrorHandler ()
+    {
+       return errorHandler;
+    }
+
+
+    /**
+     * Parse a document.
+     *
+     * @param input The input source for the document entity.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @exception java.io.IOException An IO exception from the parser,
+     *            possibly from a byte stream or character stream
+     *            supplied by the application.
+     */
+    @Override
+               public void parse (InputSource input)
+       throws SAXException, IOException
+    {
+       setupParse();
+       parent.parse(input);
+    }
+
+
+    /**
+     * Parse a document.
+     *
+     * @param systemId The system identifier as a fully-qualified URI.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @exception java.io.IOException An IO exception from the parser,
+     *            possibly from a byte stream or character stream
+     *            supplied by the application.
+     */
+    @Override
+               public void parse (String systemId)
+       throws SAXException, IOException
+    {
+       parse(new InputSource(systemId));
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.EntityResolver.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Filter an external entity resolution.
+     *
+     * @param publicId The entity's public identifier, or null.
+     * @param systemId The entity's system identifier.
+     * @return A new InputSource or null for the default.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     * @exception java.io.IOException The client may throw an
+     *            I/O-related exception while obtaining the
+     *            new InputSource.
+     */
+    @Override
+               public InputSource resolveEntity (String publicId, String systemId)
+       throws SAXException, IOException
+    {
+       if (entityResolver != null) {
+           return entityResolver.resolveEntity(publicId, systemId);
+       } else {
+           return null;
+       }
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.DTDHandler.
+    ////////////////////////////////////////////////////////////////////
+
+    
+    /**
+     * Filter a notation declaration event.
+     *
+     * @param name The notation name.
+     * @param publicId The notation's public identifier, or null.
+     * @param systemId The notation's system identifier, or null.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void notationDecl (String name, String publicId, String systemId)
+       throws SAXException
+    {
+       if (dtdHandler != null) {
+           dtdHandler.notationDecl(name, publicId, systemId);
+       }
+    }
+
+    
+    /**
+     * Filter an unparsed entity declaration event.
+     *
+     * @param name The entity name.
+     * @param publicId The entity's public identifier, or null.
+     * @param systemId The entity's system identifier, or null.
+     * @param notationName The name of the associated notation.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void unparsedEntityDecl (String name, String publicId,
+                                   String systemId, String notationName)
+       throws SAXException
+    {
+       if (dtdHandler != null) {
+           dtdHandler.unparsedEntityDecl(name, publicId, systemId,
+                                         notationName);
+       }
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.ContentHandler.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Filter a new document locator event.
+     *
+     * @param locator The document locator.
+     */
+    @Override
+               public void setDocumentLocator (Locator locator)
+    {
+       this.locator = locator;
+       if (contentHandler != null) {
+           contentHandler.setDocumentLocator(locator);
+       }
+    }
+
+
+    /**
+     * Filter a start document event.
+     *
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void startDocument ()
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.startDocument();
+       }
+    }
+
+
+    /**
+     * Filter an end document event.
+     *
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void endDocument ()
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.endDocument();
+       }
+    }
+
+
+    /**
+     * Filter a start Namespace prefix mapping event.
+     *
+     * @param prefix The Namespace prefix.
+     * @param uri The Namespace URI.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void startPrefixMapping (String prefix, String uri)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.startPrefixMapping(prefix, uri);
+       }
+    }
+
+
+    /**
+     * Filter an end Namespace prefix mapping event.
+     *
+     * @param prefix The Namespace prefix.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void endPrefixMapping (String prefix)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.endPrefixMapping(prefix);
+       }
+    }
+
+
+    /**
+     * Filter a start element event.
+     *
+     * @param uri The element's Namespace URI, or the empty string.
+     * @param localName The element's local name, or the empty string.
+     * @param qName The element's qualified (prefixed) name, or the empty
+     *        string.
+     * @param atts The element's attributes.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void startElement (String uri, String localName, String qName,
+                             Attributes atts)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.startElement(uri, localName, qName, atts);
+       }
+    }
+
+
+    /**
+     * Filter an end element event.
+     *
+     * @param uri The element's Namespace URI, or the empty string.
+     * @param localName The element's local name, or the empty string.
+     * @param qName The element's qualified (prefixed) name, or the empty
+     *        string.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void endElement (String uri, String localName, String qName)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.endElement(uri, localName, qName);
+       }
+    }
+
+
+    /**
+     * Filter a character data event.
+     *
+     * @param ch An array of characters.
+     * @param start The starting position in the array.
+     * @param length The number of characters to use from the array.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void characters (char ch[], int start, int length)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.characters(ch, start, length);
+       }
+    }
+
+
+    /**
+     * Filter an ignorable whitespace event.
+     *
+     * @param ch An array of characters.
+     * @param start The starting position in the array.
+     * @param length The number of characters to use from the array.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void ignorableWhitespace (char ch[], int start, int length)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.ignorableWhitespace(ch, start, length);
+       }
+    }
+
+
+    /**
+     * Filter a processing instruction event.
+     *
+     * @param target The processing instruction target.
+     * @param data The text following the target.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void processingInstruction (String target, String data)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.processingInstruction(target, data);
+       }
+    }
+
+
+    /**
+     * Filter a skipped entity event.
+     *
+     * @param name The name of the skipped entity.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void skippedEntity (String name)
+       throws SAXException
+    {
+       if (contentHandler != null) {
+           contentHandler.skippedEntity(name);
+       }
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.ErrorHandler.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Filter a warning event.
+     *
+     * @param e The warning as an exception.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void warning (SAXParseException e)
+       throws SAXException
+    {
+       if (errorHandler != null) {
+           errorHandler.warning(e);
+       }
+    }
+
+
+    /**
+     * Filter an error event.
+     *
+     * @param e The error as an exception.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void error (SAXParseException e)
+       throws SAXException
+    {
+       if (errorHandler != null) {
+           errorHandler.error(e);
+       }
+    }
+
+
+    /**
+     * Filter a fatal error event.
+     *
+     * @param e The error as an exception.
+     * @exception org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    @Override
+               public void fatalError (SAXParseException e)
+       throws SAXException
+    {
+       if (errorHandler != null) {
+           errorHandler.fatalError(e);
+       }
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal methods.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Set up before a parse.
+     *
+     * <p>Before every parse, check whether the parent is
+     * non-null, and re-register the filter for all of the 
+     * events.</p>
+     */
+    private void setupParse ()
+    {
+       if (parent == null) {
+           throw new NullPointerException("No parent for filter");
+       }
+       parent.setEntityResolver(this);
+       parent.setDTDHandler(this);
+       parent.setContentHandler(this);
+       parent.setErrorHandler(this);
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal state.
+    ////////////////////////////////////////////////////////////////////
+
+    private XMLReader parent = null;
+    private Locator locator = null;
+    private EntityResolver entityResolver = null;
+    private DTDHandler dtdHandler = null;
+    private ContentHandler contentHandler = null;
+    private ErrorHandler errorHandler = null;
+
+}
+
+// end of XMLFilterImpl.java
diff --git a/src/org/xml/sax/helpers/XMLReaderAdapter.java b/src/org/xml/sax/helpers/XMLReaderAdapter.java
new file mode 100644 (file)
index 0000000..8a587c4
--- /dev/null
@@ -0,0 +1,562 @@
+// XMLReaderAdapter.java - adapt an SAX2 XMLReader to a SAX1 Parser
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY!  This class is in the public domain.
+// $Id: XMLReaderAdapter.java,v 1.9 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.helpers;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import org.xml.sax.Parser;     // deprecated
+import org.xml.sax.Locator;
+import org.xml.sax.InputSource;
+import org.xml.sax.AttributeList; // deprecated
+import org.xml.sax.EntityResolver;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.DocumentHandler; // deprecated
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+
+import org.xml.sax.XMLReader;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXNotSupportedException;
+
+
+/**
+ * Adapt a SAX2 XMLReader as a SAX1 Parser.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class wraps a SAX2 {@link org.xml.sax.XMLReader XMLReader}
+ * and makes it act as a SAX1 {@link org.xml.sax.Parser Parser}.  The XMLReader 
+ * must support a true value for the 
+ * http://xml.org/sax/features/namespace-prefixes property or parsing will fail
+ * with a {@link org.xml.sax.SAXException SAXException}; if the XMLReader 
+ * supports a false value for the http://xml.org/sax/features/namespaces 
+ * property, that will also be used to improve efficiency.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.Parser
+ * @see org.xml.sax.XMLReader
+ */
+public class XMLReaderAdapter implements Parser, ContentHandler
+{
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Constructor.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Create a new adapter.
+     *
+     * <p>Use the "org.xml.sax.driver" property to locate the SAX2
+     * driver to embed.</p>
+     *
+     * @exception org.xml.sax.SAXException If the embedded driver
+     *            cannot be instantiated or if the
+     *            org.xml.sax.driver property is not specified.
+     */
+    public XMLReaderAdapter ()
+      throws SAXException
+    {
+       setup(XMLReaderFactory.createXMLReader());
+    }
+
+
+    /**
+     * Create a new adapter.
+     *
+     * <p>Create a new adapter, wrapped around a SAX2 XMLReader.
+     * The adapter will make the XMLReader act like a SAX1
+     * Parser.</p>
+     *
+     * @param xmlReader The SAX2 XMLReader to wrap.
+     * @exception java.lang.NullPointerException If the argument is null.
+     */
+    public XMLReaderAdapter (XMLReader xmlReader)
+    {
+       setup(xmlReader);
+    }
+
+
+
+    /**
+     * Internal setup.
+     *
+     * @param xmlReader The embedded XMLReader.
+     */
+    private void setup (XMLReader xmlReader)
+    {
+       if (xmlReader == null) {
+           throw new NullPointerException("XMLReader must not be null");
+       }
+       this.xmlReader = xmlReader;
+       qAtts = new AttributesAdapter();
+    }
+
+
+
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.Parser.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Set the locale for error reporting.
+     *
+     * <p>This is not supported in SAX2, and will always throw
+     * an exception.</p>
+     *
+     * @param locale the locale for error reporting.
+     * @see org.xml.sax.Parser#setLocale
+     * @exception org.xml.sax.SAXException Thrown unless overridden.
+     */
+    @Override
+               public void setLocale (Locale locale)
+       throws SAXException
+    {
+       throw new SAXNotSupportedException("setLocale not supported");
+    }
+
+
+    /**
+     * Register the entity resolver.
+     *
+     * @param resolver The new resolver.
+     * @see org.xml.sax.Parser#setEntityResolver
+     */
+    @Override
+               public void setEntityResolver (EntityResolver resolver)
+    {
+       xmlReader.setEntityResolver(resolver);
+    }
+
+
+    /**
+     * Register the DTD event handler.
+     *
+     * @param handler The new DTD event handler.
+     * @see org.xml.sax.Parser#setDTDHandler
+     */
+    @Override
+               public void setDTDHandler (DTDHandler handler)
+    {
+       xmlReader.setDTDHandler(handler);
+    }
+
+
+    /**
+     * Register the SAX1 document event handler.
+     *
+     * <p>Note that the SAX1 document handler has no Namespace
+     * support.</p>
+     *
+     * @param handler The new SAX1 document event handler.
+     * @see org.xml.sax.Parser#setDocumentHandler
+     */
+    @Override
+               public void setDocumentHandler (DocumentHandler handler)
+    {
+       documentHandler = handler;
+    }
+
+
+    /**
+     * Register the error event handler.
+     *
+     * @param handler The new error event handler.
+     * @see org.xml.sax.Parser#setErrorHandler
+     */
+    @Override
+               public void setErrorHandler (ErrorHandler handler)
+    {
+       xmlReader.setErrorHandler(handler);
+    }
+
+
+    /**
+     * Parse the document.
+     *
+     * <p>This method will throw an exception if the embedded
+     * XMLReader does not support the 
+     * http://xml.org/sax/features/namespace-prefixes property.</p>
+     *
+     * @param systemId The absolute URL of the document.
+     * @exception java.io.IOException If there is a problem reading
+     *            the raw content of the document.
+     * @exception org.xml.sax.SAXException If there is a problem
+     *            processing the document.
+     * @see #parse(org.xml.sax.InputSource)
+     * @see org.xml.sax.Parser#parse(java.lang.String)
+     */
+    @Override
+               public void parse (String systemId)
+       throws IOException, SAXException
+    {
+       parse(new InputSource(systemId));
+    }
+
+
+    /**
+     * Parse the document.
+     *
+     * <p>This method will throw an exception if the embedded
+     * XMLReader does not support the 
+     * http://xml.org/sax/features/namespace-prefixes property.</p>
+     *
+     * @param input An input source for the document.
+     * @exception java.io.IOException If there is a problem reading
+     *            the raw content of the document.
+     * @exception org.xml.sax.SAXException If there is a problem
+     *            processing the document.
+     * @see #parse(java.lang.String)
+     * @see org.xml.sax.Parser#parse(org.xml.sax.InputSource)
+     */
+    @Override
+               public void parse (InputSource input)
+       throws IOException, SAXException
+    {
+       setupXMLReader();
+       xmlReader.parse(input);
+    }
+
+
+    /**
+     * Set up the XML reader.
+     */
+    private void setupXMLReader ()
+       throws SAXException
+    {
+       xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
+       try {
+           xmlReader.setFeature("http://xml.org/sax/features/namespaces",
+                                false);
+       } catch (SAXException e) {
+           // NO OP: it's just extra information, and we can ignore it
+       }
+       xmlReader.setContentHandler(this);
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Implementation of org.xml.sax.ContentHandler.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Set a document locator.
+     *
+     * @param locator The document locator.
+     * @see org.xml.sax.ContentHandler#setDocumentLocator
+     */
+    @Override
+               public void setDocumentLocator (Locator locator)
+    {
+       if (documentHandler != null)
+           documentHandler.setDocumentLocator(locator);
+    }
+
+
+    /**
+     * Start document event.
+     *
+     * @exception org.xml.sax.SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.ContentHandler#startDocument
+     */
+    @Override
+               public void startDocument ()
+       throws SAXException
+    {
+       if (documentHandler != null)
+           documentHandler.startDocument();
+    }
+
+
+    /**
+     * End document event.
+     *
+     * @exception org.xml.sax.SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.ContentHandler#endDocument
+     */
+    @Override
+               public void endDocument ()
+       throws SAXException
+    {
+       if (documentHandler != null)
+           documentHandler.endDocument();
+    }
+
+
+    /**
+     * Adapt a SAX2 start prefix mapping event.
+     *
+     * @param prefix The prefix being mapped.
+     * @param uri The Namespace URI being mapped to.
+     * @see org.xml.sax.ContentHandler#startPrefixMapping
+     */
+    @Override
+               public void startPrefixMapping (String prefix, String uri)
+    {
+    }
+
+
+    /**
+     * Adapt a SAX2 end prefix mapping event.
+     *
+     * @param prefix The prefix being mapped.
+     * @see org.xml.sax.ContentHandler#endPrefixMapping
+     */
+    @Override
+               public void endPrefixMapping (String prefix)
+    {
+    }
+
+
+    /**
+     * Adapt a SAX2 start element event.
+     *
+     * @param uri The Namespace URI.
+     * @param localName The Namespace local name.
+     * @param qName The qualified (prefixed) name.
+     * @param atts The SAX2 attributes.
+     * @exception org.xml.sax.SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.ContentHandler#endDocument
+     */
+    @Override
+               public void startElement (String uri, String localName,
+                             String qName, Attributes atts)
+       throws SAXException
+    {
+       if (documentHandler != null) {
+           qAtts.setAttributes(atts);
+           documentHandler.startElement(qName, qAtts);
+       }
+    }
+
+
+    /**
+     * Adapt a SAX2 end element event.
+     *
+     * @param uri The Namespace URI.
+     * @param localName The Namespace local name.
+     * @param qName The qualified (prefixed) name.
+     * @exception org.xml.sax.SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.ContentHandler#endElement
+     */
+    @Override
+               public void endElement (String uri, String localName,
+                           String qName)
+       throws SAXException
+    {
+       if (documentHandler != null)
+           documentHandler.endElement(qName);
+    }
+
+
+    /**
+     * Adapt a SAX2 characters event.
+     *
+     * @param ch An array of characters.
+     * @param start The starting position in the array.
+     * @param length The number of characters to use.
+     * @exception org.xml.sax.SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.ContentHandler#characters
+     */
+    @Override
+               public void characters (char ch[], int start, int length)
+       throws SAXException
+    {
+       if (documentHandler != null)
+           documentHandler.characters(ch, start, length);
+    }
+
+
+    /**
+     * Adapt a SAX2 ignorable whitespace event.
+     *
+     * @param ch An array of characters.
+     * @param start The starting position in the array.
+     * @param length The number of characters to use.
+     * @exception org.xml.sax.SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.ContentHandler#ignorableWhitespace
+     */
+    @Override
+               public void ignorableWhitespace (char ch[], int start, int length)
+       throws SAXException
+    {
+       if (documentHandler != null)
+           documentHandler.ignorableWhitespace(ch, start, length);
+    }
+
+
+    /**
+     * Adapt a SAX2 processing instruction event.
+     *
+     * @param target The processing instruction target.
+     * @param data The remainder of the processing instruction
+     * @exception org.xml.sax.SAXException The client may raise a
+     *            processing exception.
+     * @see org.xml.sax.ContentHandler#processingInstruction
+     */
+    @Override
+               public void processingInstruction (String target, String data)
+       throws SAXException
+    {
+       if (documentHandler != null)
+           documentHandler.processingInstruction(target, data);
+    }
+
+
+    /**
+     * Adapt a SAX2 skipped entity event.
+     *
+     * @param name The name of the skipped entity.
+     * @see org.xml.sax.ContentHandler#skippedEntity
+     * @exception org.xml.sax.SAXException Throwable by subclasses.
+     */
+    @Override
+               public void skippedEntity (String name)
+       throws SAXException
+    {
+    }
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal state.
+    ////////////////////////////////////////////////////////////////////
+
+    XMLReader xmlReader;
+    DocumentHandler documentHandler;
+    AttributesAdapter qAtts;
+
+
+\f
+    ////////////////////////////////////////////////////////////////////
+    // Internal class.
+    ////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Internal class to wrap a SAX2 Attributes object for SAX1.
+     */
+    final class AttributesAdapter implements AttributeList
+    {
+       AttributesAdapter ()
+       {
+       }
+
+
+       /**
+        * Set the embedded Attributes object.
+        *
+        * @param The embedded SAX2 Attributes.
+        */ 
+       void setAttributes (Attributes attributes)
+       {
+           this.attributes = attributes;
+       }
+
+
+       /**
+        * Return the number of attributes.
+        *
+        * @return The length of the attribute list.
+        * @see org.xml.sax.AttributeList#getLength
+        */
+       @Override
+       public int getLength ()
+       {
+           return attributes.getLength();
+       }
+
+
+       /**
+        * Return the qualified (prefixed) name of an attribute by position.
+        *
+        * @return The qualified name.
+        * @see org.xml.sax.AttributeList#getName
+        */
+       @Override
+       public String getName (int i)
+       {
+           return attributes.getQName(i);
+       }
+
+
+       /**
+        * Return the type of an attribute by position.
+        *
+        * @return The type.
+        * @see org.xml.sax.AttributeList#getType(int)
+        */
+       @Override
+       public String getType (int i)
+       {
+           return attributes.getType(i);
+       }
+
+
+       /**
+        * Return the value of an attribute by position.
+        *
+        * @return The value.
+        * @see org.xml.sax.AttributeList#getValue(int)
+        */
+       @Override
+       public String getValue (int i)
+       {
+           return attributes.getValue(i);
+       }
+
+
+       /**
+        * Return the type of an attribute by qualified (prefixed) name.
+        *
+        * @return The type.
+        * @see org.xml.sax.AttributeList#getType(java.lang.String)
+        */
+       @Override
+       public String getType (String qName)
+       {
+           return attributes.getType(qName);
+       }
+
+
+       /**
+        * Return the value of an attribute by qualified (prefixed) name.
+        *
+        * @return The value.
+        * @see org.xml.sax.AttributeList#getValue(java.lang.String)
+        */
+       @Override
+       public String getValue (String qName)
+       {
+           return attributes.getValue(qName);
+       }
+
+       private Attributes attributes;
+    }
+
+}
+
+// end of XMLReaderAdapter.java
diff --git a/src/org/xml/sax/helpers/XMLReaderFactory.java b/src/org/xml/sax/helpers/XMLReaderFactory.java
new file mode 100644 (file)
index 0000000..f50be7a
--- /dev/null
@@ -0,0 +1,202 @@
+// XMLReaderFactory.java - factory for creating a new reader.
+// http://www.saxproject.org
+// Written by David Megginson
+// and by David Brownell
+// NO WARRANTY!  This class is in the Public Domain.
+// $Id: XMLReaderFactory.java,v 1.10 2002/04/22 01:00:13 dbrownell Exp $
+
+package org.xml.sax.helpers;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import org.xml.sax.XMLReader;
+import org.xml.sax.SAXException;
+
+
+/**
+ * Factory for creating an XML reader.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class contains static methods for creating an XML reader
+ * from an explicit class name, or based on runtime defaults:</p>
+ *
+ * <pre>
+ * try {
+ *   XMLReader myReader = XMLReaderFactory.createXMLReader();
+ * } catch (SAXException e) {
+ *   System.err.println(e.getMessage());
+ * }
+ * </pre>
+ *
+ * <p><strong>Note to Distributions bundled with parsers:</strong>
+ * You should modify the implementation of the no-arguments
+ * <em>createXMLReader</em> to handle cases where the external
+ * configuration mechanisms aren't set up.  That method should do its
+ * best to return a parser when one is in the class path, even when
+ * nothing bound its class name to <code>org.xml.sax.driver</code> so
+ * those configuration mechanisms would see it.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson, David Brownell
+ * @version 2.0.1 (sax2r2)
+ */
+final public class XMLReaderFactory
+{
+    /**
+     * Private constructor.
+     *
+     * <p>This constructor prevents the class from being instantiated.</p>
+     */
+    private XMLReaderFactory ()
+    {
+    }
+
+    private static final String property = "org.xml.sax.driver";
+
+    /**
+     * Attempt to create an XMLReader from system defaults.
+     * In environments which can support it, the name of the XMLReader
+     * class is determined by trying each these options in order, and
+     * using the first one which succeeds:</p> <ul>
+     *
+     * <li>If the system property <code>org.xml.sax.driver</code>
+     * has a value, that is used as an XMLReader class name. </li>
+     *
+     * <li>The JAR "Services API" is used to look for a class name
+     * in the <em>META-INF/services/org.xml.sax.driver</em> file in
+     * jarfiles available to the runtime.</li>
+     *
+     * <li> SAX parser distributions are strongly encouraged to provide
+     * a default XMLReader class name that will take effect only when
+     * previous options (on this list) are not successful.</li>
+     *
+     * <li>Finally, if {@link ParserFactory#makeParser()} can
+     * return a system default SAX1 parser, that parser is wrapped in
+     * a {@link ParserAdapter}.  (This is a migration aid for SAX1
+     * environments, where the <code>org.xml.sax.parser</code> system
+     * property will often be usable.) </li>
+     *
+     * </ul>
+     *
+     * <p> In environments such as small embedded systems, which can not
+     * support that flexibility, other mechanisms to determine the default
+     * may be used. </p>
+     *
+     * <p>Note that many Java environments allow system properties to be
+     * initialized on a command line.  This means that <em>in most cases</em>
+     * setting a good value for that property ensures that calls to this
+     * method will succeed, except when security policies intervene.
+     * This will also maximize application portability to older SAX
+     * environments, with less robust implementations of this method.
+     * </p>
+     *
+     * @return A new XMLReader.
+     * @exception org.xml.sax.SAXException If no default XMLReader class
+     *            can be identified and instantiated.
+     * @see #createXMLReader(java.lang.String)
+     */
+    public static XMLReader createXMLReader ()
+       throws SAXException
+    {
+       String          className = null;
+       ClassLoader     loader = NewInstance.getClassLoader ();
+       
+       // 1. try the JVM-instance-wide system property
+       try { className = System.getProperty (property); }
+       catch (RuntimeException e) { /* normally fails for applets */ }
+
+       // 2. if that fails, try META-INF/services/
+       if (className == null) {
+           try {
+               String          service = "META-INF/services/" + property;
+               InputStream     in;
+               BufferedReader  reader;
+
+               if (loader == null)
+                   in = ClassLoader.getSystemResourceAsStream (service);
+               else
+                   in = loader.getResourceAsStream (service);
+
+               if (in != null) {
+                   reader = new BufferedReader (
+                           new InputStreamReader (in, "UTF8"));
+                   className = reader.readLine ();
+                   in.close ();
+               }
+           } catch (Exception e) {
+           }
+       }
+
+       // 3. Distro-specific fallback
+       if (className == null) {
+// BEGIN DISTRIBUTION-SPECIFIC
+
+           // EXAMPLE:
+           // className = "com.example.sax.XmlReader";
+           // or a $JAVA_HOME/jre/lib/*properties setting...
+
+// END DISTRIBUTION-SPECIFIC
+       }
+       
+       // do we know the XMLReader implementation class yet?
+       if (className != null)
+           return loadClass (loader, className);
+
+       // 4. panic -- adapt any SAX1 parser
+       try {
+           return new ParserAdapter (ParserFactory.makeParser ());
+       } catch (Exception e) {
+           throw new SAXException ("Can't create default XMLReader; "
+                   + "is system property org.xml.sax.driver set?");
+       }
+    }
+
+
+    /**
+     * Attempt to create an XML reader from a class name.
+     *
+     * <p>Given a class name, this method attempts to load
+     * and instantiate the class as an XML reader.</p>
+     *
+     * <p>Note that this method will not be usable in environments where
+     * the caller (perhaps an applet) is not permitted to load classes
+     * dynamically.</p>
+     *
+     * @return A new XML reader.
+     * @exception org.xml.sax.SAXException If the class cannot be
+     *            loaded, instantiated, and cast to XMLReader.
+     * @see #createXMLReader()
+     */
+    public static XMLReader createXMLReader (String className)
+       throws SAXException
+    {
+       return loadClass (NewInstance.getClassLoader (), className);
+    }
+
+    private static XMLReader loadClass (ClassLoader loader, String className)
+    throws SAXException
+    {
+       try {
+           return (XMLReader) NewInstance.newInstance (loader, className);
+       } catch (ClassNotFoundException e1) {
+           throw new SAXException("SAX2 driver class " + className +
+                                  " not found", e1);
+       } catch (IllegalAccessException e2) {
+           throw new SAXException("SAX2 driver class " + className +
+                                  " found but cannot be loaded", e2);
+       } catch (InstantiationException e3) {
+           throw new SAXException("SAX2 driver class " + className +
+          " loaded but cannot be instantiated (no empty public constructor?)",
+                                  e3);
+       } catch (ClassCastException e4) {
+           throw new SAXException("SAX2 driver class " + className +
+                                  " does not implement XMLReader", e4);
+       }
+    }
+}
diff --git a/src/org/xml/sax/helpers/package.html b/src/org/xml/sax/helpers/package.html
new file mode 100644 (file)
index 0000000..8f323c0
--- /dev/null
@@ -0,0 +1,11 @@
+<HTML><HEAD>
+<!-- $Id: package.html,v 1.6 2002/01/30 20:52:39 dbrownell Exp $ -->
+</HEAD><BODY>
+
+<p>This package contains "helper" classes, including
+support for bootstrapping SAX-based applications.
+
+<p>See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+for more information about SAX.</p>
+
+</BODY></HTML>
diff --git a/swingjs/README.txt b/swingjs/README.txt
new file mode 100644 (file)
index 0000000..e16c383
--- /dev/null
@@ -0,0 +1,13 @@
+see 
+
+https://github.com/BobHanson/java2script/blob/master/sources/net.sf.j2s.core/src/net/sf/j2s/core/astvisitors/Java2ScriptVisitor.java
+
+for full list of changes
+
+recent changes in June 2018:
+
+// BH 6/24/2018 -- synchronized(a = new Object()) {...} ---> ...; only if an assignment or not a simple function call to Object.getTreeLock()
+// BH 6/23/2018 -- synchronized(a = new Object()) {...} ---> if(!(a = new Object()) {throw new NullPointerException()}else{...}
+// BH 6/21/2018 -- CharSequence.subSequence() should be defined both subSequence$I$I and subSequence
+// BH 6/20/2018 -- fixes for (int var : new int[] {3,4,5}) becoming for var var
+// BH 6/19/2018 -- adds .j2s j2s.class.replacements=org.apache.log4j.->jalview.javascript.log4j.;
diff --git a/swingjs/SwingJS-site.zip b/swingjs/SwingJS-site.zip
new file mode 100644 (file)
index 0000000..ba9ad4d
Binary files /dev/null and b/swingjs/SwingJS-site.zip differ
diff --git a/swingjs/net.sf.j2s.core.jar b/swingjs/net.sf.j2s.core.jar
new file mode 100644 (file)
index 0000000..2f523b3
Binary files /dev/null and b/swingjs/net.sf.j2s.core.jar differ
diff --git a/template.html b/template.html
new file mode 100644 (file)
index 0000000..5005060
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>SwingJS test _NAME_</title><meta charset="utf-8" />
+<script src="swingjs/swingjs2.js"></script>
+<script>
+if (!self.SwingJS)alert('swingjs2.js was not found. It needs to be in swingjs folder in the same directory as ' + document.location.href)
+Info = {
+  code: _CODE_,
+  main: _MAIN_,
+       width: 850,
+       height: 550,
+  readyFunction: null,
+       serverURL: 'https://chemapps.stolaf.edu/jmol/jsmol/php/jsmol.php',
+       j2sPath: 'swingjs/j2s',
+       console:'sysoutdiv',
+       allowjavascript: true
+}
+</script>
+</head>
+<body>
+<script>
+SwingJS.getApplet('testApplet', Info)
+getClassList = function(){J2S._saveFile('_j2sclasslist.txt', Clazz.ClassFilesLoaded.sort().join('\n'))}
+</script>
+<div style="position:absolute;left:900px;top:30px;width:600px;height:300px;">
+<div id="sysoutdiv" style="border:1px solid green;width:100%;height:95%;overflow:auto"></div>
+This is System.out. <a href="javascript:testApplet._clearConsole()">clear it</a> <br>Add ?j2snocore to URL to see full class list; ?j2sdebug to use uncompressed j2s/core files <br><a href="javascript:getClassList()">get _j2sClassList.txt</a>
+</div>
+</body>
+</html>
diff --git a/tools/ant-contrib.jar b/tools/ant-contrib.jar
new file mode 100644 (file)
index 0000000..ea817cd
Binary files /dev/null and b/tools/ant-contrib.jar differ
diff --git a/tools/closure_compiler.jar b/tools/closure_compiler.jar
new file mode 100644 (file)
index 0000000..53037b0
Binary files /dev/null and b/tools/closure_compiler.jar differ